diff --git a/Jenkinsfile b/Jenkinsfile index 09e88e86ed7..bf14f0d12d3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -92,8 +92,7 @@ pipeline { success { script { if (env.BRANCH_NAME == 'master') { - build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] - build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] + build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.17', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] } } diff --git a/README.md b/README.md index bc267f06fed..a0bd8c178f1 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ 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 v1.16.100 - v1.16.220 and Minecraft Java v1.16.4 - v1.16.5. +### Currently supporting Minecraft Bedrock 1.17 - 1.17.2 and Minecraft Java 1.17. ## Setting Up -Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. +Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. [![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4) @@ -39,13 +39,10 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Some Entity Flags - Structure block UI -## What can't be fixed -The following things cannot be fixed without changes to Bedrock. As of now, they are not fixable in Geyser. +Extended height features can be "supported", but require additional work. -- Custom heads in inventories -- Clickable links in chat -- Glowing effect -- Custom armor stand poses +## 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. ## Compiling 1. Clone the repo to your computer @@ -58,6 +55,7 @@ Any contributions are appreciated. Please feel free to reach out to us on [Disco you're interested in helping out with Geyser. ## Libraries Used: +- [Adventure Text Library](https://github.com/KyoriPowered/adventure) - [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol) - [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib) - [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index a62faa33a2c..710c8176e1a 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-bungeecord @@ -14,13 +14,13 @@ org.geysermc connector - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT compile net.md-5 bungeecord-api - 1.16-R0.4-SNAPSHOT + 1.16-R0.5-SNAPSHOT provided @@ -66,8 +66,10 @@ org.geysermc.platform.bungeecord.shaded.jackson - io.netty - org.geysermc.platform.bungeecord.shaded.netty + + io.netty.channel.kqueue + org.geysermc.platform.bungeecord.shaded.io.netty.channel.kqueue org.reflections @@ -98,6 +100,15 @@ com.google.code.gson:* org.yaml:* + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-resolver-dns:* diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java index af246f6f144..dc29b4bde46 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -41,7 +41,7 @@ public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration private Path floodgateKeyPath; public void loadFloodgate(GeyserBungeePlugin plugin) { - Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); + Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java index 12429d75bb7..4e87f3a6303 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java @@ -32,17 +32,16 @@ import org.geysermc.connector.dump.BootstrapDumpInfo; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; @Getter public class GeyserBungeeDumpInfo extends BootstrapDumpInfo { - - private String platformName; - private String platformVersion; - private boolean onlineMode; - private List listeners; - private List plugins; + private final String platformName; + private final String platformVersion; + private final boolean onlineMode; + private final List listeners; + private final List plugins; GeyserBungeeDumpInfo(ProxyServer proxy) { super(); @@ -63,7 +62,7 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo { } for (Plugin plugin : proxy.getPluginManager().getPlugins()) { - this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Arrays.asList(plugin.getDescription().getAuthor()))); + this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Collections.singletonList(plugin.getDescription().getAuthor()))); } } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java index 6eea2591898..0b8b3901b87 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -37,7 +37,6 @@ import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.ping.IGeyserPingPassthrough; -import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; @@ -64,9 +63,8 @@ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol()) ); if (event.getResponse().getPlayers().getSample() != null) { - Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> { - geyserPingInfo.getPlayerList().add(proxiedPlayer.getName()); - }); + Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> + geyserPingInfo.getPlayerList().add(proxiedPlayer.getName())); } return geyserPingInfo; } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index f4e790b8932..e89bfd9e940 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -95,10 +95,16 @@ public void onEnable() { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { + // Remove this in like a year + if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } + + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; - } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); geyserConfig.getRemote().setAuthType("floodgate"); diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index b06fc59cf5b..bf31f3fea77 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-parent pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 12c8292e911..4d295b66d91 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,15 +6,22 @@ org.geysermc bootstrap-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-spigot + + + viaversion-repo + https://repo.viaversion.com + + + org.geysermc connector - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT compile @@ -24,15 +31,15 @@ provided - us.myles + com.viaversion viaversion - 3.2.0 + 4.0.0 provided org.geysermc.geyser.adapters spigot-all - 1.1-SNAPSHOT + 1.2-SNAPSHOT @@ -68,10 +75,6 @@ - - io.netty - org.geysermc.platform.spigot.shaded.netty - it.unimi.dsi.fastutil org.geysermc.platform.spigot.shaded.fastutil @@ -100,6 +103,10 @@ net.kyori org.geysermc.platform.spigot.shaded.kyori + + org.objectweb.asm + org.geysermc.platform.spigot.shaded.asm + @@ -109,6 +116,20 @@ com.google.code.gson:* org.yaml:* + + + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-transport-native-kqueue:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-codec-dns:* + io.netty:netty-resolver-dns:* + io.netty:netty-resolver-dns-native-macos:* diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 2dbdbf830ea..e39c50bcaaf 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -42,15 +42,10 @@ public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration private Path floodgateKeyPath; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } - - @Override - public boolean isCacheChunks() { - return true; // We override this as with Bukkit, we have direct access to the server implementation - } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java index 03fa0850a65..a929e32379e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java @@ -37,13 +37,13 @@ @Getter public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { - private String platformName; - private String platformVersion; - private String platformAPIVersion; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final String platformAPIVersion; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserSpigotDumpInfo() { super(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java index 20bfecb65ad..c34d96db207 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java @@ -34,6 +34,7 @@ import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.ping.IGeyserPingPassthrough; +import javax.annotation.Nonnull; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collections; @@ -72,9 +73,10 @@ public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int max public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException { } + @Nonnull @Override public Iterator iterator() throws UnsupportedOperationException { - return Collections.EMPTY_LIST.iterator(); + return Collections.emptyIterator(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index e4bb3376a32..2bf059fee42 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -26,6 +26,10 @@ package org.geysermc.platform.spigot; import com.github.steveice10.mc.protocol.MinecraftConstants; +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 org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; @@ -47,16 +51,9 @@ import org.geysermc.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.manager.*; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -82,12 +79,6 @@ public void onEnable() { try { if (!getDataFolder().exists()) { getDataFolder().mkdir(); - File bukkitConfig = new File("plugins/Geyser-Bukkit/config.yml"); - if (bukkitConfig.exists()) { // Copy over old configs - getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copy_bukkit_config")); - Files.copy(bukkitConfig.toPath(), new File(getDataFolder().toString() + "/config.yml").toPath()); - getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copied_bukkit_config")); - } } File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); @@ -96,6 +87,23 @@ public void onEnable() { ex.printStackTrace(); } + try { + // Required for the Cloudburst Network dependency to initialize. + Class.forName("io.netty.channel.kqueue.KQueue"); + } catch (ClassNotFoundException e) { + // While we could support these older versions, the downside is not having KQueue working at all + // And since there are alternative ways to get Geyser working for these aging platforms, it's not worth it. + getLogger().severe("*********************************************"); + getLogger().severe(""); + getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); + getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.12.2")); + getLogger().severe(""); + getLogger().severe("*********************************************"); + + Bukkit.getPluginManager().disablePlugin(this); + return; + } + // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); @@ -113,11 +121,18 @@ public void onEnable() { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) { + // Remove this in like a year + if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + this.getPluginLoader().disablePlugin(this); + return; + } + + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; - } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); geyserConfig.getRemote().setAuthType("floodgate"); @@ -136,14 +151,20 @@ public void onEnable() { this.geyserSpigotPingPassthrough = new GeyserSpigotPingPassthrough(geyserLogger); } - this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); + this.geyserCommandManager = new GeyserSpigotCommandManager(connector); - boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); + boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; if (isViaVersion) { - if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { + try { + // Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version + Class.forName("com.viaversion.viaversion.api.ViaManager"); + } catch (ClassNotFoundException e) { geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", "https://ci.viaversion.com/job/ViaVersion/")); isViaVersion = false; + if (this.geyserConfig.isDebugMode()) { + e.printStackTrace(); + } } } // Used to determine if Block.getBlockData() is present. @@ -151,11 +172,6 @@ public void onEnable() { if (isLegacy) geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - boolean use3dBiomes = isCompatible(Bukkit.getServer().getVersion(), "1.16.0"); - if (!use3dBiomes) { - geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); - } - boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); // Set if we need to use a different method for getting a player's locale SpigotCommandSender.setUseLegacyLocaleMethod(isPre1_12); @@ -171,11 +187,11 @@ public void onEnable() { this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this); } else { // Post-1.13 - this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); } } else { // No ViaVersion - this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this); } geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); } catch (Exception e) { @@ -197,7 +213,7 @@ public void onEnable() { this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); } else { // Post-1.13 - this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes); + this.geyserWorldManager = new GeyserSpigotWorldManager(this); } geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } @@ -310,14 +326,14 @@ public ProtocolVersion getServerProtocolVersion() { */ private boolean isViaVersionNeeded() { ProtocolVersion serverVersion = getServerProtocolVersion(); - List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, serverVersion.getVersion()); if (protocolList == null) { // No translation needed! return false; } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { return true; } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java index c0c239b0087..a0e95850366 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java @@ -30,7 +30,6 @@ import org.bukkit.command.CommandMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandManager; -import org.geysermc.platform.spigot.GeyserSpigotPlugin; import java.lang.reflect.Field; @@ -48,12 +47,8 @@ public class GeyserSpigotCommandManager extends CommandManager { } } - private GeyserSpigotPlugin plugin; - - public GeyserSpigotCommandManager(GeyserSpigotPlugin plugin, GeyserConnector connector) { + public GeyserSpigotCommandManager(GeyserConnector connector) { super(connector); - - this.plugin = plugin; } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java index 2ee6457acdd..eff79c7ccd5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -34,6 +34,12 @@ 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; @@ -45,12 +51,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.RecipeRegistry; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import java.util.*; @@ -68,12 +68,12 @@ public class GeyserSpigot1_11CraftingListener implements Listener { /** * The list of all protocols from the client's version to 1.13. */ - private final List> protocolList; + private final List protocolList; public GeyserSpigot1_11CraftingListener(GeyserConnector connector) { this.connector = connector; - this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); } @@ -187,7 +187,7 @@ private Pair translateToBedrock(GeyserSession session, org. } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { itemId = mappingData.getNewItemId(itemId); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java index 02347f5deb8..c59cb0d77ae 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -25,6 +25,8 @@ package org.geysermc.platform.spigot.world.manager; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -32,8 +34,6 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; /** * Used with ViaVersion and pre-1.13. @@ -54,7 +54,7 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); return getLegacyBlock(storage, blockId, x, y, z); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index a28eef5b470..b50aefee724 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -25,23 +25,19 @@ package org.geysermc.platform.spigot.world.manager; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.minecraft.Position; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import org.bukkit.Bukkit; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.util.List; @@ -60,12 +56,12 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { /** * The list of all protocols from the client's version to 1.13. */ - private final List> protocolList; + private final List protocolList; public GeyserSpigot1_12WorldManager(Plugin plugin) { - super(plugin, false); - this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); - this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + super(plugin); + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); } @@ -81,7 +77,7 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); Block block = player.getWorld().getBlockAt(x, y, z); // Black magic that gets the old block state ID int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); @@ -97,7 +93,6 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { * @param z Z coordinate of block * @return the block state updated to the latest Minecraft version */ - @SuppressWarnings("deprecation") public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 blockId = mappingData1_12to1_13.getNewBlockId(blockId); @@ -109,7 +104,7 @@ public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z } } for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { blockId = mappingData.getNewBlockStateId(blockId); } @@ -117,28 +112,6 @@ public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z return blockId; } - @SuppressWarnings("deprecation") - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - if (player == null) { - return; - } - World world = player.getWorld(); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } - @Override public boolean isLegacy() { return true; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java index a9de94db5eb..8bd2c6628e5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -25,7 +25,6 @@ package org.geysermc.platform.spigot.world.manager; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; @@ -37,8 +36,7 @@ */ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { public GeyserSpigotFallbackWorldManager(Plugin plugin) { - // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. - super(plugin, false); + super(plugin); } @Override @@ -46,11 +44,6 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - // Do nothing, since we can't do anything with the chunk - } - @Override public boolean hasOwnChunkCache() { return false; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index 8f407de0a4b..253caf67019 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -26,16 +26,15 @@ package org.geysermc.platform.spigot.world.manager; import com.github.steveice10.mc.protocol.MinecraftConstants; +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 it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.platform.spigot.GeyserSpigotPlugin; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.util.List; @@ -46,18 +45,18 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl private final Int2IntMap oldToNewBlockId; - public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { - super(plugin, use3dBiomes); + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) { + super(plugin); IntList allBlockStates = adapter.getAllBlockStates(); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); - List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, serverVersion.getVersion()); for (int oldBlockId : allBlockStates) { int newBlockId = oldBlockId; // protocolList should *not* be null; we checked for that before initializing this class for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); if (mappingData != null) { newBlockId = mappingData.getNewBlockStateId(newBlockId); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java index cc9d5bddc2c..7e0b9267b5e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -36,8 +36,8 @@ public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { protected final SpigotWorldAdapter adapter; - public GeyserSpigotNativeWorldManager(Plugin plugin, boolean use3dBiomes) { - super(plugin, use3dBiomes); + public GeyserSpigotNativeWorldManager(Plugin plugin) { + super(plugin); adapter = SpigotAdapters.getWorldAdapter(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index ba61eeb72a1..6746a8d1aa9 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -25,18 +25,13 @@ package org.geysermc.platform.spigot.world.manager; -import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.Lectern; import org.bukkit.block.data.BlockData; @@ -44,17 +39,13 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.plugin.Plugin; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; -import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; -import org.geysermc.connector.utils.LanguageUtils; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -67,48 +58,10 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - /** - * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. - */ - private final boolean use3dBiomes; - /** - * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. - * - * Working with the Biome enum in Spigot poses two problems: - * 1: The Biome enum values change in both order and names over the years. - * 2: There is no way to get the Minecraft biome ID from the name itself with Spigot. - * To solve both of these problems, we store a JSON file of every Biome enum that has existed, - * along with its 1.16 biome number. - * - * The key is the Spigot Biome ordinal; the value is the Minecraft Java biome numerical ID - */ - private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - private final Plugin plugin; - public GeyserSpigotWorldManager(Plugin plugin, boolean use3dBiomes) { - this.use3dBiomes = use3dBiomes; + public GeyserSpigotWorldManager(Plugin plugin) { this.plugin = plugin; - - // Load the values into the biome-to-ID map - InputStream biomeStream = FileUtils.getResource("biomes.json"); - JsonNode biomes; - try { - biomes = GeyserConnector.JSON_MAPPER.readTree(biomeStream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - // Only load in the biomes that are present in this version of Minecraft - for (Biome enumBiome : Biome.values()) { - JsonNode biome = biomes.get(enumBiome.toString()); - if (biome != null) { - biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); - } else { - GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + - ", defaulting to 0"); - biomeToIdMap.put(enumBiome.ordinal(), 0); - } - } } @Override @@ -118,25 +71,12 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); - return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - } - - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - Player bukkitPlayer; - if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return; - } - World world = bukkitPlayer.getWorld(); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } - } + if (!world.isChunkLoaded(x >> 4, z >> 4)) { + // If the chunk isn't loaded, how could we even be here? + return BlockTranslator.JAVA_AIR_ID; } + + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); } @Override @@ -144,41 +84,6 @@ public boolean hasOwnChunkCache() { return true; } - @Override - @SuppressWarnings("deprecation") - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - int[] biomeData = new int[1024]; - World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(); - int chunkX = x << 4; - int chunkZ = z << 4; - int chunkXmax = chunkX + 16; - int chunkZmax = chunkZ + 16; - // 3D biomes didn't exist until 1.15 - if (use3dBiomes) { - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localY, localZ).ordinal(), 0); - } - } - } - } else { - // Looks like the same code, but we're not checking the Y coordinate here - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localZ).ordinal(), 0); - } - } - } - } - return biomeData; - } - @Override public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { // Run as a task to prevent async issues @@ -251,12 +156,20 @@ public boolean shouldExpectLecternHandled() { } public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { - return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); + String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()); + if (!value.isEmpty()) { + return Boolean.parseBoolean(value); + } + return (Boolean) gameRule.getDefaultValue(); } @Override public int getGameRuleInt(GeyserSession session, GameRule gameRule) { - return Integer.parseInt(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); + String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()); + if (!value.isEmpty()) { + return Integer.parseInt(value); + } + return (int) gameRule.getDefaultValue(); } @Override diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json deleted file mode 100644 index 56520e9145b..00000000000 --- a/bootstrap/spigot/src/main/resources/biomes.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "MUTATED_ICE_FLATS" : 140, - "MUTATED_TAIGA" : 133, - "SAVANNA_PLATEAU_MOUNTAINS" : 164, - "DEEP_WARM_OCEAN" : 47, - "REDWOOD_TAIGA_HILLS" : 33, - "THE_VOID" : 127, - "COLD_TAIGA_MOUNTAINS" : 158, - "BAMBOO_JUNGLE_HILLS" : 169, - "MOUNTAINS" : 3, - "MESA_PLATEAU" : 39, - "SNOWY_TAIGA_HILLS" : 31, - "DEEP_FROZEN_OCEAN" : 50, - "EXTREME_HILLS" : 3, - "BIRCH_FOREST_MOUNTAINS" : 155, - "FOREST" : 4, - "BIRCH_FOREST" : 27, - "SNOWY_TUNDRA" : 12, - "ICE_SPIKES" : 140, - "FROZEN_OCEAN" : 10, - "WARPED_FOREST" : 172, - "WOODED_BADLANDS_PLATEAU" : 38, - "BADLANDS_PLATEAU" : 39, - "ICE_PLAINS_SPIKES" : 140, - "MEGA_TAIGA" : 32, - "MUTATED_SAVANNA_ROCK" : 164, - "SAVANNA_PLATEAU" : 36, - "DARK_FOREST_HILLS" : 157, - "END_MIDLANDS" : 41, - "SHATTERED_SAVANNA_PLATEAU" : 164, - "SAVANNA" : 35, - "MUSHROOM_ISLAND_SHORE" : 15, - "SWAMP" : 6, - "ICE_MOUNTAINS" : 13, - "BEACH" : 16, - "MUTATED_MESA_CLEAR_ROCK" : 167, - "END_HIGHLANDS" : 42, - "COLD_BEACH" : 26, - "JUNGLE" : 21, - "MUTATED_TAIGA_COLD" : 158, - "TALL_BIRCH_HILLS" : 156, - "DARK_FOREST" : 29, - "WOODED_HILLS" : 18, - "HELL" : 8, - "MUTATED_REDWOOD_TAIGA" : 160, - "MESA_PLATEAU_FOREST" : 38, - "MUSHROOM_ISLAND" : 14, - "BADLANDS" : 37, - "END_BARRENS" : 43, - "MUTATED_EXTREME_HILLS_WITH_TREES" : 162, - "MUTATED_JUNGLE_EDGE" : 151, - "MODIFIED_BADLANDS_PLATEAU" : 167, - "ROOFED_FOREST_MOUNTAINS" : 157, - "SOUL_SAND_VALLEY" : 170, - "DESERT" : 2, - "MUTATED_PLAINS" : 129, - "MUTATED_BIRCH_FOREST" : 155, - "WOODED_MOUNTAINS" : 34, - "TAIGA_HILLS" : 19, - "BAMBOO_JUNGLE" : 168, - "SWAMPLAND_MOUNTAINS" : 134, - "DESERT_MOUNTAINS" : 130, - "REDWOOD_TAIGA" : 32, - "MUSHROOM_FIELDS" : 14, - "GIANT_TREE_TAIGA_HILLS" : 33, - "PLAINS" : 1, - "JUNGLE_EDGE" : 23, - "SAVANNA_MOUNTAINS" : 163, - "DEEP_COLD_OCEAN" : 49, - "DESERT_LAKES" : 130, - "MOUNTAIN_EDGE" : 20, - "SNOWY_MOUNTAINS" : 13, - "MESA_PLATEAU_MOUNTAINS" : 167, - "JUNGLE_MOUNTAINS" : 149, - "SMALLER_EXTREME_HILLS" : 20, - "MESA_PLATEAU_FOREST_MOUNTAINS" : 166, - "NETHER_WASTES" : 8, - "BIRCH_FOREST_HILLS_MOUNTAINS" : 156, - "MUTATED_JUNGLE" : 149, - "WARM_OCEAN" : 44, - "DEEP_OCEAN" : 24, - "STONE_BEACH" : 25, - "MODIFIED_JUNGLE" : 149, - "MUTATED_SAVANNA" : 163, - "TAIGA_COLD_HILLS" : 31, - "OCEAN" : 0, - "SMALL_END_ISLANDS" : 40, - "MUSHROOM_FIELD_SHORE" : 15, - "GRAVELLY_MOUNTAINS" : 131, - "FROZEN_RIVER" : 11, - "TAIGA_COLD" : 30, - "BASALT_DELTAS" : 173, - "EXTREME_HILLS_WITH_TREES" : 34, - "MEGA_TAIGA_HILLS" : 33, - "MUTATED_FOREST" : 132, - "MUTATED_BIRCH_FOREST_HILLS" : 156, - "SKY" : 9, - "LUKEWARM_OCEAN" : 45, - "EXTREME_HILLS_MOUNTAINS" : 131, - "COLD_TAIGA_HILLS" : 31, - "THE_END" : 9, - "SUNFLOWER_PLAINS" : 129, - "SAVANNA_ROCK" : 36, - "ERODED_BADLANDS" : 165, - "STONE_SHORE" : 25, - "EXTREME_HILLS_PLUS_MOUNTAINS" : 162, - "CRIMSON_FOREST" : 171, - "VOID" : 127, - "SNOWY_TAIGA" : 30, - "SNOWY_TAIGA_MOUNTAINS" : 158, - "FLOWER_FOREST" : 132, - "COLD_OCEAN" : 46, - "BEACHES" : 16, - "MESA" : 37, - "MUSHROOM_SHORE" : 15, - "MESA_CLEAR_ROCK" : 39, - "NETHER" : 8, - "ICE_PLAINS" : 12, - "SHATTERED_SAVANNA" : 163, - "ROOFED_FOREST" : 29, - "GIANT_SPRUCE_TAIGA_HILLS" : 161, - "SNOWY_BEACH" : 26, - "MESA_BRYCE" : 165, - "JUNGLE_EDGE_MOUNTAINS" : 151, - "MUTATED_DESERT" : 130, - "MODIFIED_GRAVELLY_MOUNTAINS" : 158, - "MEGA_SPRUCE_TAIGA" : 160, - "TAIGA_MOUNTAINS" : 133, - "SMALL_MOUNTAINS" : 20, - "EXTREME_HILLS_PLUS" : 34, - "GIANT_SPRUCE_TAIGA" : 160, - "FOREST_HILLS" : 18, - "DESERT_HILLS" : 17, - "MUTATED_REDWOOD_TAIGA_HILLS" : 161, - "MEGA_SPRUCE_TAIGA_HILLS" : 161, - "RIVER" : 7, - "GIANT_TREE_TAIGA" : 32, - "SWAMPLAND" : 6, - "JUNGLE_HILLS" : 22, - "TALL_BIRCH_FOREST" : 155, - "DEEP_LUKEWARM_OCEAN" : 48, - "MESA_ROCK" : 38, - "SWAMP_HILLS" : 134, - "MODIFIED_WOODED_BADLANDS_PLATEAU" : 166, - "MODIFIED_JUNGLE_EDGE" : 151, - "BIRCH_FOREST_HILLS" : 28, - "COLD_TAIGA" : 30, - "TAIGA" : 5, - "MUTATED_MESA_ROCK" : 166, - "MUTATED_SWAMPLAND" : 134, - "ICE_FLATS" : 12, - "MUTATED_ROOFED_FOREST" : 157, - "MUTATED_MESA" : 165, - "MUTATED_EXTREME_HILLS" : 131 -} diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index fee71ab1f08..0cd78ebe3b9 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -3,9 +3,28 @@ name: ${outputName}-Spigot author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} -softdepend: ["ViaVersion"] +softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser help \ No newline at end of file + usage: /geyser help +permissions: + geyser.command.help: + description: Shows help for all registered commands. + geyser.command.advancement: + description: Shows the advancements of the player on the server. + geyser.command.dump: + description: Dumps Geyser debug information for bug reports. + geyser.command.list: + description: List all players connected through Geyser. + geyser.command.offhand: + description: Puts an items in your offhand. + geyser.command.reload: + description: Reloads the Geyser configurations. Kicks all players when used! + geyser.command.shutdown: + description: Shuts down Geyser. + geyser.command.statistics: + description: Shows the statistics of the player on the server. + geyser.command.version: + description: Shows the current Geyser version and checks for updates. \ No newline at end of file diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 93ae3ab6b41..72733a31135 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java index d36ba311039..2872e5abcba 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java @@ -36,13 +36,12 @@ @Getter public class GeyserSpongeDumpInfo extends BootstrapDumpInfo { - - private String platformName; - private String platformVersion; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserSpongeDumpInfo() { super(); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index 8ef23b19e4a..0e46413ccae 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -33,7 +33,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import org.spongepowered.api.command.CommandCallable; -import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.text.Text; @@ -81,7 +80,7 @@ public CommandResult process(CommandSource source, String arguments) { } @Override - public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { + public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { if (arguments.split(" ").length == 1) { return connector.getCommandManager().getCommandNames(); } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java index 1f6eaa695ce..38d8ba6061c 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java @@ -32,8 +32,7 @@ import org.spongepowered.api.text.Text; public class GeyserSpongeCommandManager extends CommandManager { - - private org.spongepowered.api.command.CommandManager handle; + private final org.spongepowered.api.command.CommandManager handle; public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserConnector connector) { super(connector); @@ -43,6 +42,8 @@ public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager h @Override public String getDescription(String command) { - return handle.get(command).map(CommandMapping::getCallable).map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)).orElse(Text.EMPTY).toPlain(); + return handle.get(command).map(CommandMapping::getCallable) + .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) + .orElse(Text.EMPTY).toPlain(); } } diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index b2abd3ad837..e8d7a67e698 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT compile diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java index 2577ce0332c..f7716b5a23f 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java @@ -30,8 +30,7 @@ @Getter public class GeyserStandaloneDumpInfo extends BootstrapDumpInfo { - - private boolean isGui; + private final boolean isGui; GeyserStandaloneDumpInfo(GeyserStandaloneBootstrap bootstrap) { super(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java index 7eeba84bd7c..5db34dbab8b 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java @@ -30,7 +30,6 @@ import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.OpenOption; import java.nio.file.Paths; public class LoopbackUtil { @@ -54,7 +53,7 @@ public static void checkLoopback(GeyserStandaloneLogger geyserLogger) { String result = sb.toString(); if (!result.contains("minecraftuwp")) { - Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]); + Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes()); Runtime.getRuntime().exec(startScript); geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added")); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java index 10309395402..db5c4a3e1e5 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java @@ -60,8 +60,8 @@ private void append(Color c, String s) { */ public void appendANSI(String s) { // convert ANSI color codes first int aPos = 0; // current char position in addString - int aIndex = 0; // index of next Escape sequence - int mIndex = 0; // index of "m" terminating Escape sequence + int aIndex; // index of next Escape sequence + int mIndex; // index of "m" terminating Escape sequence String tmpString = ""; boolean stillSearching = true; // true until no more Escape sequences String addString = remaining + s; @@ -83,7 +83,6 @@ public void appendANSI(String s) { // convert ANSI color codes first // while there's text in the input buffer - stillSearching = true; while (stillSearching) { mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence if (mIndex < 0) { // the buffer ends halfway through the ansi string! diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java index ebcc8f82c9a..68adfde52b5 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java @@ -46,7 +46,7 @@ public final class GraphPanel extends JPanel { private final static Color pointColor = new Color(100, 100, 100, 255); private final static Color gridColor = new Color(200, 200, 200, 255); private static final Stroke graphStroke = new BasicStroke(2f); - private List values = new ArrayList<>(10); + private final List values = new ArrayList<>(10); @Setter private String xLabel = ""; @@ -172,6 +172,7 @@ protected void paintComponent(Graphics graphics) { for (Point graphPoint : graphPoints) { final int x = graphPoint.x - pointWidth / 2; final int y = graphPoint.y - pointWidth / 2; + //noinspection SuspiciousNameCombination g.fillOval(x, y, pointWidth, pointWidth); } } diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index ccbc40c3853..0025b018573 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT compile @@ -107,6 +107,7 @@ io.netty:netty-resolver:* io.netty:netty-transport:* io.netty:netty-codec:* + io.netty:netty-codec-haproxy:* org.slf4j:* org.ow2.asm:* diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java index 261e8fef885..93af72d84a2 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java @@ -37,13 +37,13 @@ @Getter public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { - private String platformName; - private String platformVersion; - private String platformVendor; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final String platformVendor; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserVelocityDumpInfo(ProxyServer proxy) { super(); @@ -61,7 +61,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { for (PluginContainer plugin : proxy.getPluginManager().getPlugins()) { String pluginClass = plugin.getInstance().map((pl) -> pl.getClass().getName()).orElse("unknown"); - this.plugins.add(new PluginInfo(true, plugin.getDescription().getName().get(), plugin.getDescription().getVersion().get(), pluginClass, plugin.getDescription().getAuthors())); + this.plugins.add(new PluginInfo(true, plugin.getDescription().getName().orElse(null), plugin.getDescription().getVersion().orElse(null), pluginClass, plugin.getDescription().getAuthors())); } } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index cc71ffc4277..0c68a41d71b 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -108,6 +108,15 @@ public void onEnable() { this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Remove this in like a year + try { + // Should only exist on 1.0 + Class.forName("org.geysermc.floodgate.FloodgateAPI"); + geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } catch (ClassNotFoundException ignored) { + } + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; diff --git a/common/pom.xml b/common/pom.xml index e40e40c55d2..8ddcfd9813c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,22 +6,20 @@ org.geysermc geyser-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT common - com.google.code.gson - gson - 2.8.2 - compile + org.geysermc.cumulus + cumulus + 1.0-SNAPSHOT - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 - compile + com.google.code.gson + gson + 2.8.6 \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java index a1096f347a5..098fd39464f 100644 --- a/common/src/main/java/org/geysermc/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -31,7 +31,6 @@ @Getter @AllArgsConstructor public enum PlatformType { - ANDROID("Android"), BUNGEECORD("BungeeCord"), FABRIC("Fabric"), diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java deleted file mode 100644 index f4e597f003b..00000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.common.window; - -import lombok.Getter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.FormComponent; -import org.geysermc.common.window.response.CustomFormResponse; - -public class CustomFormBuilder { - - @Getter - private CustomFormWindow form; - - public CustomFormBuilder(String title) { - form = new CustomFormWindow(title); - } - - public CustomFormBuilder setTitle(String title) { - form.setTitle(title); - return this; - } - - public CustomFormBuilder setIcon(FormImage icon) { - form.setIcon(icon); - return this; - } - - public CustomFormBuilder setResponse(String data) { - form.setResponse(data); - return this; - } - - public CustomFormBuilder setResponse(CustomFormResponse response) { - form.setResponse(response); - return this; - } - - public CustomFormBuilder addComponent(FormComponent component) { - form.addComponent(component); - return this; - } - - public CustomFormWindow build() { - return form; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index 7043dda9f07..00000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.*; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CustomFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private FormImage icon; - - @Getter - private List content; - - public CustomFormWindow(String title) { - this(title, new ArrayList<>()); - } - - public CustomFormWindow(String title, List content) { - this(title, content, (FormImage) null); - } - - public CustomFormWindow(String title, List content, String icon) { - this(title, content, new FormImage(FormImage.FormImageType.URL, icon)); - } - - public CustomFormWindow(String title, List content, FormImage icon) { - super("custom_form"); - - this.title = title; - this.content = content; - this.icon = icon; - } - - public void addComponent(FormComponent component) { - content.add(component); - } - - public String getJSONData() { - String toModify = ""; - try { - toModify = new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { } - - //We need to replace this due to Java not supporting declaring class field 'default' - return toModify.replace("defaultOptionIndex", "default") - .replace("defaultText", "default") - .replace("defaultValue", "default") - .replace("defaultStepIndex", "default"); - } - - public void setResponse(String data) { - if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { - closed = true; - return; - } - - int i = 0; - Map dropdownResponses = new HashMap(); - Map inputResponses = new HashMap(); - Map sliderResponses = new HashMap(); - Map stepSliderResponses = new HashMap(); - Map toggleResponses = new HashMap(); - Map responses = new HashMap(); - Map labelResponses = new HashMap(); - - List componentResponses = new ArrayList<>(); - try { - componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); - } catch (IOException e) { } - - for (String response : componentResponses) { - if (i >= content.size()) { - break; - } - - FormComponent component = content.get(i); - if (component == null) - return; - - if (component instanceof LabelComponent) { - LabelComponent labelComponent = (LabelComponent) component; - labelResponses.put(i, labelComponent.getText()); - } - - if (component instanceof DropdownComponent) { - DropdownComponent dropdownComponent = (DropdownComponent) component; - String option = dropdownComponent.getOptions().get(Integer.parseInt(response)); - - dropdownResponses.put(i, new FormResponseData(Integer.parseInt(response), option)); - responses.put(i, option); - } - - if (component instanceof InputComponent) { - inputResponses.put(i, response); - responses.put(i, response); - } - - if (component instanceof SliderComponent) { - float value = Float.parseFloat(response); - sliderResponses.put(i, value); - responses.put(i, value); - } - - if (component instanceof StepSliderComponent) { - StepSliderComponent stepSliderComponent = (StepSliderComponent) component; - String step = stepSliderComponent.getSteps().get(Integer.parseInt(response)); - stepSliderResponses.put(i, new FormResponseData(Integer.parseInt(response), step)); - responses.put(i, step); - } - - if (component instanceof ToggleComponent) { - boolean answer = Boolean.parseBoolean(response); - toggleResponses.put(i, answer); - responses.put(i, answer); - } - i++; - } - - this.response = new CustomFormResponse(responses, dropdownResponses, inputResponses, - sliderResponses, stepSliderResponses, toggleResponses, labelResponses); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java deleted file mode 100644 index 9d802161786..00000000000 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.ModalFormResponse; - -public class ModalFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private String button1; - - @Getter - @Setter - private String button2; - - public ModalFormWindow(String title, String content, String button1, String button2) { - super("modal"); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - if (Boolean.parseBoolean(data)) { - response = new ModalFormResponse(0, button1); - } else { - response = new ModalFormResponse(1, button2); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index 48bda0bd238..00000000000 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.List; - - -public class SimpleFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private List buttons; - - public SimpleFormWindow(String title, String content) { - this(title, content, new ArrayList()); - } - - public SimpleFormWindow(String title, String content, List buttons) { - super("form"); - - this.title = title; - this.content = content; - this.buttons = buttons; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.trim().equalsIgnoreCase("null")) { - closed = true; - return; - } - - int buttonID; - try { - buttonID = Integer.parseInt(data.trim()); - } catch (Exception ex) { - return; - } - - if (buttonID >= buttons.size()) { - response = new SimpleFormResponse(buttonID, null); - return; - } - - response = new SimpleFormResponse(buttonID, buttons.get(buttonID)); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java deleted file mode 100644 index 3e681664c95..00000000000 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -public class SliderComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private float min; - - @Getter - @Setter - private float max; - - @Getter - @Setter - private int step; - - @Getter - @Setter - private float defaultValue; - - public SliderComponent(String text, float min, float max, int step, float defaultValue) { - super("slider"); - - this.text = text; - this.min = Math.max(min, 0f); - this.max = max > this.min ? max : this.min; - if (step != -1f && step > 0) - this.step = step; - - if (defaultValue != -1f) - this.defaultValue = defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java deleted file mode 100644 index e79e7592a6f..00000000000 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -public class ToggleComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private boolean defaultValue; - - public ToggleComponent(String text) { - this(text, false); - } - - public ToggleComponent(String text, boolean defaultValue) { - super("toggle"); - - this.text = text; - this.defaultValue = defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java deleted file mode 100644 index 252a0978b49..00000000000 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.util.Map; - -@Getter -@AllArgsConstructor -public class CustomFormResponse implements FormResponse { - - private Map responses; - private Map dropdownResponses; - private Map inputResponses; - private Map sliderResponses; - private Map stepSliderResponses; - private Map toggleResponses; - private Map labelResponses; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java deleted file mode 100644 index c24f72d0282..00000000000 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class SimpleFormResponse implements FormResponse { - - private int clickedButtonId; - private FormButton clickedButton; -} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java new file mode 100644 index 00000000000..f602f4be034 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import lombok.RequiredArgsConstructor; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.SecureRandom; + +@RequiredArgsConstructor +public final class AesCipher implements FloodgateCipher { + public static final int IV_LENGTH = 12; + private static final int TAG_BIT_LENGTH = 128; + private static final String CIPHER_NAME = "AES/GCM/NoPadding"; + + private final SecureRandom secureRandom = new SecureRandom(); + private final Topping topping; + private SecretKey secretKey; + + public void init(Key key) { + if (!"AES".equals(key.getAlgorithm())) { + throw new RuntimeException( + "Algorithm was expected to be AES, but got " + key.getAlgorithm() + ); + } + secretKey = (SecretKey) key; + } + + public byte[] encrypt(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + byte[] iv = new byte[IV_LENGTH]; + secureRandom.nextBytes(iv); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); + byte[] cipherText = cipher.doFinal(data); + + if (topping != null) { + iv = topping.encode(iv); + cipherText = topping.encode(cipherText); + } + + return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1) + .put(IDENTIFIER) // header + .put(iv) + .put((byte) 0x21) + .put(cipherText) + .array(); + } + + public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { + checkHeader(cipherTextWithIv); + + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; + ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); + + int ivLength = IV_LENGTH; + + if (topping != null) { + int mark = buffer.position(); + + // we need the first index, the second is for the optional RawSkin + boolean found = false; + while (buffer.hasRemaining() && !found) { + if (buffer.get() == 0x21) { + found = true; + } + } + + ivLength = buffer.position() - mark - 1; // don't include the splitter itself + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(mark); // reset to the pre-while index + } + + byte[] iv = new byte[ivLength]; + buffer.get(iv); + + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(buffer.position() + 1); // skip splitter + + byte[] cipherText = new byte[buffer.remaining()]; + buffer.get(cipherText); + + if (topping != null) { + iv = topping.decode(iv); + cipherText = topping.decode(cipherText); + } + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); + return cipher.doFinal(cipherText); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java new file mode 100644 index 00000000000..faec0ad107f --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public final class AesKeyProducer implements KeyProducer { + public static int KEY_SIZE = 128; + + @Override + public SecretKey produce() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(KEY_SIZE, getSecureRandom()); + return keyGenerator.generateKey(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + @Override + public SecretKey produceFrom(byte[] keyFileData) { + try { + return new SecretKeySpec(keyFileData, "AES"); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { + // use Windows-PRNG for windows (default impl is SHA1PRNG) + if (System.getProperty("os.name").startsWith("Windows")) { + return SecureRandom.getInstance("Windows-PRNG"); + } else { + try { + // NativePRNG (which should be the default on unix-systems) can still block your + // system. Even though it isn't as bad as NativePRNGBlocking, we still try to + // prevent that if possible + return SecureRandom.getInstance("NativePRNGNonBlocking"); + } catch (NoSuchAlgorithmException ignored) { + // at this point we just have to go with the default impl even if it blocks + return new SecureRandom(); + } + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java similarity index 78% rename from common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java rename to common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java index 3338df4da9e..18fc5a04a0a 100644 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java @@ -23,16 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.floodgate.crypto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; +import java.util.Base64; -@Getter -@AllArgsConstructor -public class ModalFormResponse implements FormResponse { +public final class Base64Topping implements Topping { + @Override + public byte[] encode(byte[] data) { + return Base64.getEncoder().encode(data); + } - private int clickedButtonId; - private String clickedButtonText; + @Override + public byte[] decode(byte[] data) { + return Base64.getDecoder().decode(data); + } } diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java new file mode 100644 index 00000000000..69c8f9f6dfd --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.geysermc.floodgate.util.InvalidFormatException; + +import java.nio.charset.StandardCharsets; +import java.security.Key; + +/** + * Responsible for both encrypting and decrypting data + */ +public interface FloodgateCipher { + // use invalid username characters at the beginning and the end of the identifier, + // to make sure that it doesn't get messed up with usernames + byte[] IDENTIFIER = "^Floodgate^".getBytes(StandardCharsets.UTF_8); + int HEADER_LENGTH = IDENTIFIER.length; + + static boolean hasHeader(String data) { + if (data.length() < IDENTIFIER.length) { + return false; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Initializes the instance by giving it the key it needs to encrypt or decrypt data + * + * @param key the key used to encrypt and decrypt data + */ + void init(Key key); + + /** + * Encrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + byte[] encrypt(byte[] data) throws Exception; + + /** + * Encrypts data from a String.
This method internally calls {@link #encrypt(byte[])} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + default byte[] encryptFromString(String data) throws Exception { + return encrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to decrypt + * @return the decrypted data + * @throws Exception when the decrypting failed + */ + byte[] decrypt(byte[] data) throws Exception; + + /** + * Decrypts a byte[] and turn it into a String.
This method internally calls {@link + * #decrypt(byte[])} and converts the returned byte[] into a String. + * + * @param data the data to encrypt + * @return the decrypted data in a UTF-8 String + * @throws Exception when the decrypting failed + */ + default String decryptToString(byte[] data) throws Exception { + byte[] decrypted = decrypt(data); + if (decrypted == null) { + return null; + } + return new String(decrypted, StandardCharsets.UTF_8); + } + + /** + * Decrypts a String.
This method internally calls {@link #decrypt(byte[])} by converting + * the UTF-8 String into a byte[] + * + * @param data the data to decrypt + * @return the decrypted data in a byte[] + * @throws Exception when the decrypting failed + */ + default byte[] decryptFromString(String data) throws Exception { + return decrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Checks if the header is valid. This method will throw an InvalidFormatException when the + * header is invalid. + * + * @param data the data to check + * @throws InvalidFormatException when the header is invalid + */ + default void checkHeader(byte[] data) throws InvalidFormatException { + final int identifierLength = IDENTIFIER.length; + + if (data.length <= HEADER_LENGTH) { + throw new InvalidFormatException("Data length is smaller then header." + + "Needed " + HEADER_LENGTH + ", got " + data.length, + true + ); + } + + for (int i = 0; i < identifierLength; i++) { + if (IDENTIFIER[i] != data[i]) { + StringBuilder receivedIdentifier = new StringBuilder(); + for (byte b : IDENTIFIER) { + receivedIdentifier.append(b); + } + + throw new InvalidFormatException( + String.format("Expected identifier %s, got %s", + new String(IDENTIFIER, StandardCharsets.UTF_8), + receivedIdentifier.toString() + ), + true + ); + } + } + } + + @Data + @AllArgsConstructor + class HeaderResult { + private int version; + private int startIndex; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityDestroyPacketReceive.java b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java similarity index 65% rename from connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityDestroyPacketReceive.java rename to common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java index 599f0b975d8..fc2ac512d0e 100644 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityDestroyPacketReceive.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java @@ -20,19 +20,22 @@ * THE SOFTWARE. * * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser + * @link https://github.com/GeyserMC/Floodgate * */ -package org.geysermc.connector.event.events.packet.downstream; +package org.geysermc.floodgate.crypto; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; -public class ServerEntityDestroyPacketReceive extends DownstreamPacketReceiveEvent { - public ServerEntityDestroyPacketReceive(@NonNull GeyserSession session, @NonNull ServerEntityDestroyPacket packet) { - super(session, packet); +public interface KeyProducer { + Key produce(); + Key produceFrom(byte[] keyFileData); + + default Key produceFrom(Path keyFileLocation) throws IOException { + return produceFrom(Files.readAllBytes(keyFileLocation)); } } diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java similarity index 84% rename from common/src/main/java/org/geysermc/common/window/response/FormResponseData.java rename to common/src/main/java/org/geysermc/floodgate/crypto/Topping.java index 013c4985999..689274269bc 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java @@ -23,15 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.floodgate.crypto; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class FormResponseData { - - private int elementID; - private String elementContent; +public interface Topping { + byte[] encode(byte[] data); + byte[] decode(byte[] data); } diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java new file mode 100644 index 00000000000..be0634a6d42 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java @@ -0,0 +1,143 @@ +/* + * 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.floodgate.news; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class NewsItem { + private final int id; + private final String project; + private final boolean active; + private final NewsType type; + private final ItemData data; + private final boolean priority; + private final String message; + private final Set actions; + private final String url; + + private NewsItem(int id, String project, boolean active, NewsType type, ItemData data, + boolean priority, String message, Set actions, String url) { + this.id = id; + this.project = project; + this.active = active; + this.type = type; + this.data = data; + this.priority = priority; + this.message = message; + this.actions = Collections.unmodifiableSet(actions); + this.url = url; + } + + public static NewsItem readItem(JsonObject newsItem) { + NewsType newsType = NewsType.getByName(newsItem.get("type").getAsString()); + if (newsType == null) { + return null; + } + + JsonObject messageObject = newsItem.getAsJsonObject("message"); + NewsItemMessage itemMessage = NewsItemMessage.getById(messageObject.get("id").getAsInt()); + + String message = "Received an unknown news message type. Please update"; + if (itemMessage != null) { + message = itemMessage.getFormattedMessage(messageObject.getAsJsonArray("args")); + } + + Set actions = new HashSet<>(); + for (JsonElement actionElement : newsItem.getAsJsonArray("actions")) { + NewsItemAction action = NewsItemAction.getByName(actionElement.getAsString()); + if (action != null) { + actions.add(action); + } + } + + return new NewsItem( + newsItem.get("id").getAsInt(), + newsItem.get("project").getAsString(), + newsItem.get("active").getAsBoolean(), + newsType, + newsType.read(newsItem.getAsJsonObject("data")), + newsItem.get("priority").getAsBoolean(), + message, + actions, + newsItem.get("url").getAsString() + ); + } + + public int getId() { + return id; + } + + public String getProject() { + return project; + } + + public boolean isGlobal() { + return "all".equals(getProject()); + } + + public boolean isActive() { + return active; + } + + public NewsType getType() { + return type; + } + + public ItemData getData() { + return data; + } + + @SuppressWarnings("unchecked") + public T getDataAs(Class type) { + return (T) data; + } + + public boolean isPriority() { + return priority; + } + + public String getRawMessage() { + return message; + } + + public String getMessage() { + return message + " See " + getUrl() + " for more information."; + } + + public Set getActions() { + return actions; + } + + public String getUrl() { + return url; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java new file mode 100644 index 00000000000..78a8e4ed3d6 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java @@ -0,0 +1,44 @@ +/* + * 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.floodgate.news; + +public enum NewsItemAction { + ON_SERVER_STARTED, + ON_OPERATOR_JOIN, + BROADCAST_TO_CONSOLE, + BROADCAST_TO_OPERATORS; + + private static final NewsItemAction[] VALUES = values(); + + public static NewsItemAction getByName(String actionName) { + for (NewsItemAction type : VALUES) { + if (type.name().equalsIgnoreCase(actionName)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java new file mode 100644 index 00000000000..b11605fb476 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java @@ -0,0 +1,89 @@ +/* + * 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.floodgate.news; + +import com.google.gson.JsonArray; + +// {} is used for things that have to be filled in by the server, +// {@} is for things that have to be filled in by us +public enum NewsItemMessage { + UPDATE_AVAILABLE("There is an update available for {}. The newest version is: {}"), + UPDATE_RECOMMENDED(UPDATE_AVAILABLE + ". Your version is quite old, updating is recommend."), + UPDATE_HIGHLY_RECOMMENDED(UPDATE_AVAILABLE + ". We highly recommend updating because some important changes have been made."), + UPDATE_ANCIENT_VERSION(UPDATE_AVAILABLE + ". You are running an ancient version, updating is recommended."), + + DOWNTIME_GENERIC("The {} is temporarily going down for maintenance soon."), + DOWNTIME_WITH_START("The {} is temporarily going down for maintenance on {}."), + DOWNTIME_TIMEFRAME(DOWNTIME_WITH_START + " The maintenance is expected to last till {}."); + + private static final NewsItemMessage[] VALUES = values(); + + private final String messageFormat; + private final String[] messageSplitted; + + NewsItemMessage(String messageFormat) { + this.messageFormat = messageFormat; + this.messageSplitted = messageFormat.split(" "); + } + + public static NewsItemMessage getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public String getMessageFormat() { + return messageFormat; + } + + public String getFormattedMessage(JsonArray serverArguments) { + int serverArgumentsIndex = 0; + + StringBuilder message = new StringBuilder(); + for (String split : messageSplitted) { + if (message.length() > 0) { + message.append(' '); + } + + String result = split; + + if (serverArgumentsIndex < serverArguments.size()) { + String argument = serverArguments.get(serverArgumentsIndex).getAsString(); + result = result.replace("{}", argument); + if (!result.equals(split)) { + serverArgumentsIndex++; + } + } + + message.append(result); + } + return message.toString(); + } + + + @Override + public String toString() { + return getMessageFormat(); + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java similarity index 56% rename from common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java rename to common/src/main/java/org/geysermc/floodgate/news/NewsType.java index 8e6748a9426..c2848535ebb 100644 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java @@ -23,47 +23,37 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.floodgate.news; -import lombok.Getter; -import lombok.Setter; +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; +import org.geysermc.floodgate.news.data.ItemData; -import java.util.ArrayList; -import java.util.List; +import java.util.function.Function; -public class StepSliderComponent extends FormComponent { +public enum NewsType { + BUILD_SPECIFIC(BuildSpecificData::read), + CHECK_AFTER(CheckAfterData::read); - @Getter - @Setter - private String text; + private static final NewsType[] VALUES = values(); - @Getter - private List steps; + private final Function readFunction; - @Getter - @Setter - private int defaultStepIndex; - - public StepSliderComponent(String text) { - this(text, new ArrayList()); + NewsType(Function readFunction) { + this.readFunction = readFunction; } - public StepSliderComponent(String text, List steps) { - this(text, steps, 0); + public static NewsType getByName(String newsType) { + for (NewsType type : VALUES) { + if (type.name().equalsIgnoreCase(newsType)) { + return type; + } + } + return null; } - public StepSliderComponent(String text, List steps, int defaultStepIndex) { - super("step_slider"); - - this.text = text; - this.steps = steps; - this.defaultStepIndex = defaultStepIndex; - } - - public void addStep(String step, boolean isDefault) { - steps.add(step); - - if (isDefault) - defaultStepIndex = steps.size() - 1; + public ItemData read(JsonObject data) { + return readFunction.apply(data); } } diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java new file mode 100644 index 00000000000..d415f420082 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java @@ -0,0 +1,60 @@ +/* + * 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.floodgate.news.data; + +import com.google.gson.JsonObject; + +public final class BuildSpecificData implements ItemData { + private String branch; + + private boolean allAffected; + private int affectedGreaterThan; + private int affectedLessThan; + + public static BuildSpecificData read(JsonObject data) { + BuildSpecificData updateData = new BuildSpecificData(); + updateData.branch = data.get("branch").getAsString(); + + JsonObject affectedBuilds = data.getAsJsonObject("affected_builds"); + if (affectedBuilds.has("all")) { + updateData.allAffected = affectedBuilds.get("all").getAsBoolean(); + } + if (!updateData.allAffected) { + updateData.affectedGreaterThan = affectedBuilds.get("gt").getAsInt(); + updateData.affectedLessThan = affectedBuilds.get("lt").getAsInt(); + } + return updateData; + } + + public boolean isAffected(String branch, int buildId) { + return this.branch.equals(branch) && + (allAffected || buildId > affectedGreaterThan && buildId < affectedLessThan); + } + + public String getBranch() { + return branch; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java new file mode 100644 index 00000000000..92d01689b39 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java @@ -0,0 +1,42 @@ +/* + * 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.floodgate.news.data; + +import com.google.gson.JsonObject; + +public class CheckAfterData implements ItemData { + private long checkAfter; + + public static CheckAfterData read(JsonObject data) { + CheckAfterData checkAfterData = new CheckAfterData(); + checkAfterData.checkAfter = data.get("check_after").getAsLong(); + return checkAfterData; + } + + public long getCheckAfter() { + return checkAfter; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java similarity index 94% rename from common/src/main/java/org/geysermc/common/window/response/FormResponse.java rename to common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java index 35023f167e1..122ee775ddb 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.floodgate.news.data; -public interface FormResponse { +public interface ItemData { } diff --git a/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java new file mode 100644 index 00000000000..9e7f2c1b0b8 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java @@ -0,0 +1,91 @@ +/* + * 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.floodgate.time; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +/* + * Thanks: + * https://datatracker.ietf.org/doc/html/rfc1769 + * https://github.com/jonsagara/SimpleNtpClient + * https://stackoverflow.com/a/29138806 + */ +public final class SntpClientUtils { + private static final int NTP_PORT = 123; + + private static final int NTP_PACKET_SIZE = 48; + private static final int NTP_MODE = 3; // client + private static final int NTP_VERSION = 3; + private static final int RECEIVE_TIME_POSITION = 32; + + private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L; + + public static long requestTimeOffset(String host, int timeout) { + try (DatagramSocket socket = new DatagramSocket()) { + socket.setSoTimeout(timeout); + + InetAddress address = InetAddress.getByName(host); + + ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE); + + DatagramPacket request = new DatagramPacket( + buff.array(), NTP_PACKET_SIZE, address, NTP_PORT + ); + + // mode is in the least signification 3 bits + // version is in bits 3-5 + buff.put((byte) (NTP_MODE | (NTP_VERSION << 3))); + + long originateTime = System.currentTimeMillis(); + socket.send(request); + + DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE); + socket.receive(response); + + long responseTime = System.currentTimeMillis(); + + // everything before isn't important for us + buff.position(RECEIVE_TIME_POSITION); + + long receiveTime = readTimestamp(buff); + long transmitTime = readTimestamp(buff); + + return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; + } catch (Exception ignored) { + } + return Long.MIN_VALUE; + } + + private static long readTimestamp(ByteBuffer buffer) { + //todo look into the ntp 2036 problem + long seconds = buffer.getInt() & 0xffffffffL; + long fraction = buffer.getInt() & 0xffffffffL; + return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java new file mode 100644 index 00000000000..3fe089e060d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -0,0 +1,67 @@ +/* + * 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.floodgate.time; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public final class TimeSyncer { + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset + + public TimeSyncer(String timeServer) { + executorService.scheduleWithFixedDelay(() -> { + // 5 tries to get the time offset, since UDP doesn't guaranty a response + for (int i = 0; i < 5; i++) { + long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); + if (offset != Long.MIN_VALUE) { + timeOffset = offset; + return; + } + } + }, 0, 30, TimeUnit.MINUTES); + } + + public void shutdown() { + executorService.shutdown(); + } + + public long getTimeOffset() { + return timeOffset; + } + + public long getRealMillis() { + if (hasUsefulOffset()) { + return System.currentTimeMillis() + getTimeOffset(); + } + return System.currentTimeMillis(); + } + + public boolean hasUsefulOffset() { + return timeOffset != Long.MIN_VALUE; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java similarity index 84% rename from common/src/main/java/org/geysermc/common/window/component/FormComponent.java rename to common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java index 6b503eb49a7..326fa2590d0 100644 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java @@ -23,16 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.floodgate.util; -import lombok.Getter; - -public abstract class FormComponent { - - @Getter - private final String type; - - public FormComponent(String type) { - this.type = type; +public final class Base64Utils { + public static int getEncodedLength(int length) { + if (length <= 0) { + return -1; + } + return 4 * ((length + 2) / 3); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 22c33f47502..4f4325a9b0c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -25,47 +25,91 @@ package org.geysermc.floodgate.util; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.time.TimeSyncer; -import java.util.UUID; - -@AllArgsConstructor +/** + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This + * class is only used internally, and you should look at FloodgatePlayer instead (FloodgatePlayer is + * present in the API module of the Floodgate repo) + */ @Getter -public class BedrockData { - public static final int EXPECTED_LENGTH = 7; - public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class BedrockData implements Cloneable { + public static final int EXPECTED_LENGTH = 13; + + private final String version; + private final String username; + private final String xuid; + private final int deviceOs; + private final String languageCode; + private final int uiProfile; + private final int inputMode; + private final String ip; + private final LinkedPlayer linkedPlayer; + private final boolean fromProxy; - private String version; - private String username; - private String xuid; - private int deviceId; - private String languageCode; - private int inputMode; - private String ip; - private int dataLength; + private final int subscribeId; + private final String verifyCode; + + private final long timestamp; + private final int dataLength; + + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode, TimeSyncer timeSyncer) { + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, + timeSyncer.getRealMillis(), EXPECTED_LENGTH); + } - public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) { - this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH); + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode, TimeSyncer timeSyncer) { + return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, + false, subscribeId, verifyCode, timeSyncer); } public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return null; + if (split.length != EXPECTED_LENGTH) { + return emptyData(split.length); + } + LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); + // The format is the same as the order of the fields in this class return new BedrockData( - split[0], split[1], split[2], Integer.parseInt(split[3]), - split[4], Integer.parseInt(split[5]), split[6], split.length + split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1, + dataLength); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; } @Override public String toString() { - return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ - inputMode +'\0'+ ip; + // The format is the same as the order of the fields in this class + return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + + (fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode + '\0' + timestamp; + } + + @Override + public BedrockData clone() throws CloneNotSupportedException { + return (BedrockData) super.clone(); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java similarity index 70% rename from common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java rename to common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index 1e0b22cf6c0..714af6f1fc9 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -25,36 +25,41 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; -public enum DeviceOS { - - @JsonEnumDefaultValue +/** + * The Operation Systems where Bedrock players can connect with + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum DeviceOs { UNKNOWN("Unknown"), - ANDROID("Android"), + GOOGLE("Android"), IOS("iOS"), OSX("macOS"), - FIREOS("FireOS"), + AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - WIN10("Windows 10"), - WIN32("Windows"), + UWP("Windows 10"), + WIN32("Windows x86"), DEDICATED("Dedicated"), - ORBIS("PS4"), + TVOS("Apple TV"), + PS4("PS4"), NX("Switch"), - SWITCH("Switch"), - XBOX_ONE("Xbox One"), - WIN_PHONE("Windows Phone"); + XBOX("Xbox One"), + WINDOWS_PHONE("Windows Phone"); - private static final DeviceOS[] VALUES = values(); + private static final DeviceOs[] VALUES = values(); private final String displayName; - DeviceOS(final String displayName) { - this.displayName = displayName; - } - - public static DeviceOS getById(int id) { + /** + * Get the DeviceOs instance from the identifier. + * + * @param id the DeviceOs identifier + * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static DeviceOs getById(int id) { return id < VALUES.length ? VALUES[id] : VALUES[0]; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java deleted file mode 100644 index f4dd5d2e3f1..00000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.floodgate.util; - -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.*; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -public class EncryptionUtil { - public static String encrypt(Key key, String data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedText = cipher.doFinal(data.getBytes()); - - cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' + - Base64.getEncoder().encodeToString(encryptedText); - } - - public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); - } - - public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - String[] split = encryptedData.split("\0"); - if (split.length != 2) { - throw new IllegalArgumentException("Expected two arguments, got " + split.length); - } - - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0])); - - SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); - cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - return cipher.doFinal(Base64.getDecoder().decode(split[1])); - } - - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); - } - - @SuppressWarnings("unchecked") - public static T getKeyFromFile(Path fileLocation, Class keyType) throws - IOException, InvalidKeySpecException, NoSuchAlgorithmException { - boolean isPublicKey = keyType == PublicKey.class; - if (!isPublicKey && keyType != PrivateKey.class) { - throw new RuntimeException("I can only read public and private keys!"); - } - - byte[] key = Files.readAllBytes(fileLocation); - - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key); - return (T) (isPublicKey ? - keyFactory.generatePublic(keySpec) : - keyFactory.generatePrivate(keySpec) - ); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java similarity index 85% rename from common/src/main/java/org/geysermc/common/window/component/LabelComponent.java rename to common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java index 4608282fc21..c7a681f9d56 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java @@ -23,20 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; -public class LabelComponent extends FormComponent { +import java.util.Properties; +public final class FloodgateInfoHolder { @Getter @Setter - private String text; - - public LabelComponent(String text) { - super("label"); - - this.text = text; - } + private static Object config; + @Getter + @Setter + private static Properties gitProperties; } diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerWorldBorderPacketReceive.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerWorldBorderPacketReceive.java rename to common/src/main/java/org/geysermc/floodgate/util/InputMode.java index cb64d9ce65f..d49d2ea841f 100644 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerWorldBorderPacketReceive.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -24,15 +24,24 @@ * */ -package org.geysermc.connector.event.events.packet.downstream; +package org.geysermc.floodgate.util; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerWorldBorderPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; +public enum InputMode { + UNKNOWN, + KEYBOARD_MOUSE, + TOUCH, + CONTROLLER, + VR; -public class ServerWorldBorderPacketReceive extends DownstreamPacketReceiveEvent { - public ServerWorldBorderPacketReceive(@NonNull GeyserSession session, @NonNull ServerWorldBorderPacket packet) { - super(session, packet); + private static final InputMode[] VALUES = values(); + + /** + * Get the InputMode instance from the identifier. + * + * @param id the InputMode identifier + * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static InputMode getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; } -} +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerTitlePacketReceive.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerTitlePacketReceive.java rename to common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java index a6d8eb8bee1..9ec5b171091 100644 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerTitlePacketReceive.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java @@ -24,15 +24,28 @@ * */ -package org.geysermc.connector.event.events.packet.downstream; +package org.geysermc.floodgate.util; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerTitlePacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; +import lombok.Getter; -public class ServerTitlePacketReceive extends DownstreamPacketReceiveEvent { - public ServerTitlePacketReceive(@NonNull GeyserSession session, @NonNull ServerTitlePacket packet) { - super(session, packet); +@Getter +public class InvalidFormatException extends Exception { + private boolean header = false; + + public InvalidFormatException() { + super(); + } + + public InvalidFormatException(String message) { + super(message); + } + + public InvalidFormatException(String message, boolean header) { + super(message); + this.header = header; + } + + public InvalidFormatException(String message, Throwable cause) { + super(message, cause); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java new file mode 100644 index 00000000000..f91bfafbcda --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class LinkedPlayer implements Cloneable { + /** + * The Java username of the linked player + */ + private final String javaUsername; + /** + * The Java UUID of the linked player + */ + private final UUID javaUniqueId; + /** + * The UUID of the Bedrock player + */ + private final UUID bedrockId; + /** + * If the LinkedPlayer is sent from a different platform. For example the LinkedPlayer is from + * Bungee but the data has been sent to the Bukkit server. + */ + private boolean fromDifferentPlatform = false; + + public static LinkedPlayer of(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); + } + + public static LinkedPlayer fromString(String data) { + String[] split = data.split(";"); + if (split.length != 3) { + return null; + } + + LinkedPlayer player = new LinkedPlayer( + split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) + ); + player.fromDifferentPlatform = true; + return player; + } + + @Override + public String toString() { + return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); + } + + @Override + public LinkedPlayer clone() throws CloneNotSupportedException { + return (LinkedPlayer) super.clone(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerCombatPacketReceive.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerCombatPacketReceive.java rename to common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index da4230d04ec..af1121f3c8b 100644 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerCombatPacketReceive.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -24,15 +24,21 @@ * */ -package org.geysermc.connector.event.events.packet.downstream; +package org.geysermc.floodgate.util; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerCombatPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; +public enum UiProfile { + CLASSIC, + POCKET; -public class ServerCombatPacketReceive extends DownstreamPacketReceiveEvent { - public ServerCombatPacketReceive(@NonNull GeyserSession session, @NonNull ServerCombatPacket packet) { - super(session, packet); + private static final UiProfile[] VALUES = values(); + + /** + * Get the UiProfile instance from the identifier. + * + * @param id the UiProfile identifier + * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found + */ + public static UiProfile getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java new file mode 100644 index 00000000000..f24dc8145d1 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -0,0 +1,91 @@ +/* + * 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.floodgate.util; + +public enum WebsocketEventType { + /** + * Sent once we successfully connected to the server + */ + SUBSCRIBER_CREATED(0), + /** + * Sent every time a subscriber got added or disconnected + */ + SUBSCRIBER_COUNT(1), + /** + * Sent once the creator disconnected. After this packet the server will automatically close the + * connection once the queue size (sent in {@link #ADDED_TO_QUEUE} and {@link #SKIN_UPLOADED} + * reaches 0. + */ + CREATOR_DISCONNECTED(4), + + /** + * Sent every time a skin got added to the upload queue + */ + ADDED_TO_QUEUE(2), + /** + * Sent every time a skin got successfully uploaded + */ + SKIN_UPLOADED(3), + + /** + * Sent every time a news item was added + */ + NEWS_ADDED(6), + + /** + * Sent when the server wants you to know something. Currently used for violations that aren't + * bad enough to close the connection + */ + LOG_MESSAGE(5); + + private static final WebsocketEventType[] VALUES; + + static { + WebsocketEventType[] values = values(); + VALUES = new WebsocketEventType[values.length]; + for (WebsocketEventType value : values) { + VALUES[value.id] = value; + } + } + + /** + * The ID is based of the time it got added. However, to keep the enum organized as time goes on, + * it looks nicer to sort the events based of categories. + */ + private final int id; + + WebsocketEventType(int id) { + this.id = id; + } + + public static WebsocketEventType getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public int getId() { + return id; + } +} diff --git a/connector/pom.xml b/connector/pom.xml index 5da4e92c078..d610988b32f 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,33 +6,59 @@ org.geysermc geyser-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT connector - 4.1.59.Final + 4.8.0 8.5.2 - 4.7.0 + 2.10.2 + 4.1.59.Final org.geysermc common - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT + compile + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} compile com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.10.2 + ${jackson.version} + compile + + + org.java-websocket + Java-WebSocket + 1.5.1 compile com.github.CloudburstMC.Protocol - bedrock-v431 - f8ecf54 + bedrock-v440 + 1656151 compile @@ -120,9 +146,15 @@ compile - com.github.steveice10 - mcprotocollib - 26201a4 + com.github.GeyserMC + MCAuthLib + 0e48a094f2 + compile + + + com.github.GeyserMC + MCProtocolLib + 7248769 compile @@ -142,7 +174,7 @@ com.github.GeyserMC PacketLib - b77a427 + 0b75570 compile @@ -203,12 +235,15 @@ org.reflections reflections 0.9.11 + compile org.dom4j dom4j 2.1.3 + compile + net.kyori adventure-api @@ -359,6 +394,7 @@ String GIT_VERSION = ".*" + String GIT_VERSION = "git-${git.branch}-${git.commit.id.abbrev}" diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index 77492fb7a66..7f5bca8c268 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -33,11 +33,20 @@ public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + if (!config.getRemote().getAuthType().equals("floodgate")) { + return geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { + if (config.getFloodgateKeyFile().equals("public-key.pem")) { + logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); + floodgateKey = geyserDataFolder.resolve("key.pem"); + } + + if (!Files.exists(floodgateKey)) { if (floodgate != null) { - Path autoKey = floodgateDataFolder.resolve("public-key.pem"); + Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) { logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 239aaa7a5b2..aab8ee70b96 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -28,9 +28,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.network.util.EventLoops; import com.nukkitx.protocol.bedrock.BedrockServer; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.kqueue.KQueue; import lombok.Getter; import lombok.Setter; import org.geysermc.common.PlatformType; @@ -58,8 +61,15 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.event.events.geyser.GeyserStopEvent; import org.geysermc.connector.utils.*; +import org.geysermc.floodgate.crypto.AesCipher; +import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.Base64Topping; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.news.NewsItemAction; +import org.geysermc.floodgate.time.TimeSyncer; import org.jetbrains.annotations.Contract; import javax.naming.directory.Attribute; @@ -67,16 +77,18 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.security.Key; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Getter public class GeyserConnector { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper() .enable(JsonParser.Feature.IGNORE_UNDEFINED) .enable(JsonParser.Feature.ALLOW_COMMENTS) @@ -87,7 +99,7 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs public static final String VERSION = "DEV"; // A fallback for running in IDEs - public static final String MINECRAFT_VERSION = "1.16.4 - 1.16.5"; + public static final String MINECRAFT_VERSION = MinecraftConstants.GAME_VERSION; // Change if multiple version strings are supported /** * Oauth client ID for Microsoft authentication @@ -100,14 +112,25 @@ public class GeyserConnector { private static GeyserConnector instance; + /** + * This is used in GeyserConnect to stop the bedrock server binding to a port + */ + @Setter + private static boolean shouldStartListener = true; + @Setter private AuthType defaultAuthType; + private final TimeSyncer timeSyncer; + private FloodgateCipher cipher; + private FloodgateSkinUploader skinUploader; + private final NewsHandler newsHandler; + private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; - private BedrockServer bedrockServer; + private final BedrockServer bedrockServer; private final PlatformType platformType; private final GeyserBootstrap bootstrap; @@ -199,7 +222,41 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); - CooldownUtils.setShowCooldown(config.getShowCooldown()); + TimeSyncer timeSyncer = null; + if (defaultAuthType == AuthType.FLOODGATE) { + timeSyncer = new TimeSyncer(Constants.NTP_SERVER); + try { + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); + cipher = new AesCipher(new Base64Topping()); + cipher.init(key); + logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + skinUploader = new FloodgateSkinUploader(this).start(); + } catch (Exception exception) { + logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + } + } + this.timeSyncer = timeSyncer; + + String branch = "unknown"; + int buildNumber = -1; + if (this.isProductionEnvironment()) { + try { + Properties gitProperties = new Properties(); + gitProperties.load(FileUtils.getResource("git.properties")); + branch = gitProperties.getProperty("git.branch"); + String build = gitProperties.getProperty("git.build.number"); + if (build != null) { + buildNumber = Integer.parseInt(build); + } + } catch (Throwable e) { + logger.error("Failed to read git.properties", e); + } + } else { + logger.debug("Not getting git properties for the news handler as we are in a development environment."); + } + newsHandler = new NewsHandler(branch, buildNumber); + + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); @@ -214,15 +271,31 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { EventLoops.commonGroup(), enableProxyProtocol ); - bedrockServer.setHandler(new ConnectorServerEventHandler(this)); - bedrockServer.bind().whenComplete((avoid, throwable) -> { - if (throwable == null) { - logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); - } else { - logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); - throwable.printStackTrace(); + + if (config.isDebugMode()) { + logger.debug("EventLoop type: " + EventLoops.getChannelType()); + if (EventLoops.getChannelType() == EventLoops.ChannelType.NIO) { + if (System.getProperties().contains("disableNativeEventLoop")) { + logger.debug("EventLoop type is NIO because native event loops are disabled."); + } else { + logger.debug("Reason for no Epoll: " + Epoll.unavailabilityCause().toString()); + logger.debug("Reason for no KQueue: " + KQueue.unavailabilityCause().toString()); + } } - }).join(); + } + + bedrockServer.setHandler(new ConnectorServerEventHandler(this)); + + if (shouldStartListener) { + bedrockServer.bind().whenComplete((avoid, throwable) -> { + if (throwable == null) { + logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); + } else { + logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); + throwable.printStackTrace(); + } + }).join(); + } if (config.getMetrics().isEnabled()) { metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); @@ -237,7 +310,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { for (GeyserSession session : players) { if (session == null) continue; if (session.getClientData() == null) continue; - String os = session.getClientData().getDeviceOS().toString(); + String os = session.getClientData().getDeviceOs().toString(); if (!valueMap.containsKey(os)) { valueMap.put(os, 1); } else { @@ -274,6 +347,40 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { return versionMap; })); } + + // The following code can be attributed to the PaperMC project + // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614 + metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + // http://openjdk.java.net/jeps/223 + // Java decided to change their versioning scheme and in doing so modified the + // java.version system property to return $major[.$minor][.$security][-ea], as opposed to + // 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1", + // otherwise, 9+ + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + // of course, it really wouldn't be all that simple if they didn't add a quirk, now + // would it valid strings for the major may potentially include values such as -ea to + // denote a pre release + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + return map; + })); } boolean isGui = false; @@ -302,6 +409,8 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { if (platformType == PlatformType.STANDALONE) { logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn")); } + + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); } public void shutdown() { @@ -352,6 +461,13 @@ public void run() { generalThreadPool.shutdown(); bedrockServer.close(); + if (timeSyncer != null) { + timeSyncer.shutdown(); + } + if (skinUploader != null) { + skinUploader.close(); + } + newsHandler.shutdown(); players.clear(); defaultAuthType = null; this.getCommandManager().getCommands().clear(); @@ -430,17 +546,31 @@ public WorldManager getWorldManager() { return bootstrap.getWorldManager(); } + public TimeSyncer getTimeSyncer() { + return timeSyncer; + } + + /** + * Returns false if this Geyser instance is running in an IDE. This only needs to be used in cases where files + * expected to be in a jarfile are not present. + * + * @return true if the version number is not 'DEV'. + */ + public boolean isProductionEnvironment() { + //noinspection ConstantConditions - changes in production + return !"DEV".equals(GeyserConnector.VERSION); + } + /** * Whether to use XML reflections in the jar or manually find the reflections. - * Will return true if the version number is not 'DEV' and the platform is not Fabric. + * Will return true if in production and the platform is not Fabric. * On Fabric - it complains about being unable to create a default XMLReader. * On other platforms this should only be true in compiled jars. * * @return whether to use XML reflections */ public boolean useXmlReflections() { - //noinspection ConstantConditions - return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION); + return !this.getPlatformType().equals(PlatformType.FABRIC) && isProductionEnvironment(); } /** diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 9f675ae81ed..f62fd853943 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public CommandManager(GeyserConnector connector) { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java index 2ef23381b99..8ace83840fd 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -25,25 +25,20 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; public class AdvancementsCommand extends GeyserCommand { - - public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) { + public AdvancementsCommand(String name, String description, String permission) { super(name, description, permission); } @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - if (session == null) return; - - SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); - session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID); + if (session != null) { + session.getAdvancementsCache().buildAndShowMenuForm(); + } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index c2716f20695..53968e40bd5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -38,8 +38,7 @@ import java.util.stream.Collectors; public class HelpCommand extends GeyserCommand { - - public GeyserConnector connector; + private final GeyserConnector connector; public HelpCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java index 8a000f80cd2..915a062a759 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java @@ -45,8 +45,7 @@ public ListCommand(GeyserConnector connector, String name, String description, S @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - String message = ""; - message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + String message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index 4d7d74045ec..47ca821b9a4 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -25,7 +25,6 @@ package org.geysermc.connector.command.defaults; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; @@ -33,15 +32,12 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.BlockUtils; public class OffhandCommand extends GeyserCommand { - private final GeyserConnector connector; - public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); - - this.connector = connector; } @Override @@ -50,7 +46,7 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) return; } - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO, BlockFace.DOWN); session.sendDownstreamPacket(releaseItemPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 2f1c7dc9b2d..d3f3d2f3ffa 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -32,6 +32,8 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; +import java.util.ArrayList; + public class ReloadCommand extends GeyserCommand { private final GeyserConnector connector; @@ -51,8 +53,8 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) sender.sendMessage(message); - for (GeyserSession otherSession : connector.getPlayers()) { - otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); + for (GeyserSession otherSession : new ArrayList<>(connector.getPlayers())) { + otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", otherSession.getLocale())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java index 2aeee137791..0f85e3c8f6f 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java @@ -32,17 +32,15 @@ import org.geysermc.connector.utils.SettingsUtils; public class SettingsCommand extends GeyserCommand { - public SettingsCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); } @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - if (session == null) return; - - SettingsUtils.buildForm(session); - session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID); + if (session != null) { + session.sendForm(SettingsUtils.buildForm(session)); + } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index 21a891c0e0b..48026e84e34 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -25,7 +25,6 @@ package org.geysermc.connector.command.defaults; -import com.github.steveice10.mc.protocol.MinecraftConstants; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; @@ -67,8 +66,7 @@ public void execute(GeyserSession session, CommandSender sender, String[] args) sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, GeyserConnector.MINECRAFT_VERSION, bedrockVersions)); // Disable update checking in dev mode and for players in Geyser Standalone - //noinspection ConstantConditions - changes in production - if (!GeyserConnector.VERSION.equals("DEV") && !(!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE)) { + if (GeyserConnector.getInstance().isProductionEnvironment() && !(!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE)) { sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); try { Properties gitProp = new Properties(); diff --git a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java index f91da11b590..8490dc7f250 100644 --- a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java +++ b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java @@ -84,7 +84,8 @@ private boolean isHeadless() { Class graphicsEnvironment = Class.forName("java.awt.GraphicsEnvironment"); Method isHeadless = graphicsEnvironment.getDeclaredMethod("isHeadless"); return (Boolean)isHeadless.invoke(null); - } catch (Exception ex) { } + } catch (Exception ignored) { + } return true; } diff --git a/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java b/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java index 0b723817254..8ea6ef220ab 100644 --- a/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java +++ b/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java @@ -51,20 +51,25 @@ public class AsteriskSerializer extends StdSerializer implements Context @JsonSerialize(using = AsteriskSerializer.class) public @interface Asterisk { String value() default "***"; - boolean sensitive() default false; + /** + * If true, this value will be shown if {@link #showSensitive} is true, or if the IP is determined to not be a public IP + * + * @return true if this should be analyzed and treated as an IP + */ + boolean isIp() default false; } String asterisk; - boolean sensitive; + boolean isIp; public AsteriskSerializer() { super(Object.class); } - public AsteriskSerializer(String asterisk, boolean sensitive) { + public AsteriskSerializer(String asterisk, boolean isIp) { super(Object.class); this.asterisk = asterisk; - this.sensitive = sensitive; + this.isIp = isIp; } @Override @@ -72,16 +77,25 @@ public JsonSerializer createContextual(SerializerProvider serializerProvider, Optional anno = Optional.ofNullable(property) .map(prop -> prop.getAnnotation(Asterisk.class)); - return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::sensitive).orElse(null)); + return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::isIp).orElse(null)); } @Override public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException { - if (sensitive && showSensitive) { + if (isIp && (showSensitive || !isSensitiveIp((String) obj))) { gen.writeObject(obj); return; } gen.writeString(asterisk); } + + private boolean isSensitiveIp(String ip) { + if (ip.equalsIgnoreCase("localhost") || ip.equalsIgnoreCase("auto")) { + // `auto` should not be shown unless there is an obscure issue with setting the localhost address + return false; + } + + return !ip.isEmpty() && !ip.equals("0.0.0.0") && !ip.equals("127.0.0.1"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/configuration/EmoteOffhandWorkaroundOption.java b/connector/src/main/java/org/geysermc/connector/configuration/EmoteOffhandWorkaroundOption.java new file mode 100644 index 00000000000..954e3d32a39 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/configuration/EmoteOffhandWorkaroundOption.java @@ -0,0 +1,53 @@ +/* + * 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.connector.configuration; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +public enum EmoteOffhandWorkaroundOption { + NO_EMOTES, + EMOTES_AND_OFFHAND, + DISABLED; + + public static class Deserializer extends JsonDeserializer { + @Override + public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + switch (value) { + case "no-emotes": + return NO_EMOTES; + case "emotes-and-offhand": + return EMOTES_AND_OFFHAND; + default: + return DISABLED; + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index d1d40ea9c5c..5b910dc68ca 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -77,6 +77,8 @@ public interface GeyserConfiguration { boolean isShowCoordinates(); + EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); + String getDefaultLocale(); Path getFloodgateKeyPath(); @@ -85,8 +87,6 @@ public interface GeyserConfiguration { boolean isAboveBedrockNetherBuilding(); - boolean isCacheChunks(); - boolean isForceResourcePacks(); boolean isXboxAchievementsEnabled(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 9a400031d2e..dbafcf715f7 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.GeyserConnector; @@ -100,15 +101,16 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("show-coordinates") private boolean showCoordinates = true; + @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) + @JsonProperty("emote-offhand-workaround") + private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; + @JsonProperty("allow-third-party-ears") private boolean allowThirdPartyEars = false; @JsonProperty("default-locale") private String defaultLocale = null; // is null by default so system language takes priority - @JsonProperty("cache-chunks") - private boolean cacheChunks = false; - @JsonProperty("cache-images") private int cacheImages = 0; @@ -132,7 +134,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @Getter @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { - @AsteriskSerializer.Asterisk(sensitive = true) + @AsteriskSerializer.Asterisk(isIp = true) private String address = "0.0.0.0"; @Setter @@ -182,7 +184,7 @@ public List getWhitelistedIPsMatchers() { @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter - @AsteriskSerializer.Asterisk(sensitive = true) + @AsteriskSerializer.Asterisk(isIp = true) private String address = "auto"; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 4f16de90393..afcbda6a412 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -34,8 +34,7 @@ @Getter public class BootstrapDumpInfo { - - private PlatformType platform; + private final PlatformType platform; public BootstrapDumpInfo() { this.platform = GeyserConnector.getInstance().getPlatformType(); @@ -43,8 +42,7 @@ public BootstrapDumpInfo() { @Getter @AllArgsConstructor - public class PluginInfo { - + public static class PluginInfo { public boolean enabled; public String name; public String version; @@ -54,8 +52,7 @@ public class PluginInfo { @Getter @AllArgsConstructor - public class ListenerInfo { - + public static class ListenerInfo { public String ip; public int port; } diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index 9af740d99cf..fee4ac7a4dc 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -27,8 +27,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteSource; +import com.google.common.io.Files; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.serializer.AsteriskSerializer; @@ -37,8 +41,10 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.FloodgateInfoHolder; +import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -48,32 +54,54 @@ @Getter public class DumpInfo { - @JsonIgnore private static final long MEGABYTE = 1024L * 1024L; private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private Object2IntMap userPlatforms; - private RamInfo ramInfo; + private final Floodgate floodgate; + private final Object2IntMap userPlatforms; + private final HashInfo hashInfo; + private final RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; public DumpInfo() { - this.versionInfo = new DumpInfo.VersionInfo(); + this.versionInfo = new VersionInfo(); try { this.gitInfo = new Properties(); this.gitInfo.load(FileUtils.getResource("git.properties")); - } catch (IOException ignored) { } + } catch (IOException ignored) { + } this.config = GeyserConnector.getInstance().getConfig(); + this.floodgate = new Floodgate(); + + String md5Hash = "unknown"; + String sha256Hash = "unknown"; + try { + // https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file + // https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java + File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + ByteSource byteSource = Files.asByteSource(file); + // Jenkins uses MD5 for its hash + //noinspection UnstableApiUsage + md5Hash = byteSource.hash(Hashing.md5()).toString(); + //noinspection UnstableApiUsage + sha256Hash = byteSource.hash(Hashing.sha256()).toString(); + } catch (Exception e) { + if (GeyserConnector.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + } + this.hashInfo = new HashInfo(md5Hash, sha256Hash); this.ramInfo = new DumpInfo.RamInfo(); - this.userPlatforms = new Object2IntOpenHashMap(); + this.userPlatforms = new Object2IntOpenHashMap<>(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOS device = session.getClientData().getDeviceOS(); + DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } @@ -81,8 +109,7 @@ public DumpInfo() { } @Getter - public class VersionInfo { - + public static class VersionInfo { private final String name; private final String version; private final String javaVersion; @@ -97,7 +124,8 @@ public class VersionInfo { this.name = GeyserConnector.NAME; this.version = GeyserConnector.VERSION; this.javaVersion = System.getProperty("java.version"); - this.architecture = System.getProperty("os.arch"); // Usually gives Java architecture but still may be helpful. + // Usually gives Java architecture but still may be helpful. + this.architecture = System.getProperty("os.arch"); this.operatingSystem = System.getProperty("os.name"); this.operatingSystemVersion = System.getProperty("os.version"); @@ -108,9 +136,8 @@ public class VersionInfo { @Getter public static class NetworkInfo { - - private String internalIP; private final boolean dockerCheck; + private String internalIP; NetworkInfo() { if (AsteriskSerializer.showSensitive) { @@ -123,7 +150,8 @@ public static class NetworkInfo { try { // Fallback to the normal way of getting the local IP this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } + } catch (UnknownHostException ignored) { + } } } else { // Sometimes the internal IP is the external IP... @@ -136,7 +164,6 @@ public static class NetworkInfo { @Getter public static class MCInfo { - private final String bedrockVersion; private final int bedrockProtocol; private final String javaVersion; @@ -151,8 +178,25 @@ public static class MCInfo { } @Getter - public static class RamInfo { + public static class Floodgate { + private final Properties gitInfo; + private final Object config; + + Floodgate() { + this.gitInfo = FloodgateInfoHolder.getGitProperties(); + this.config = FloodgateInfoHolder.getConfig(); + } + } + @AllArgsConstructor + @Getter + public static class HashInfo { + private final String md5Hash; + private final String sha256Hash; + } + + @Getter + public static class RamInfo { private final long free; private final long total; private final long max; diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java index 70dbdf95933..54bec7257a3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -36,12 +36,15 @@ public class AbstractArrowEntity extends Entity { public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Set the correct texture if using the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, entityType == EntityType.SPECTRAL_ARROW); + setMotion(motion); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { byte data = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.CRITICAL, (data & 0x01) == 0x01); diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java index 6f3d0204f2f..5a33bc98cd7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -52,14 +52,14 @@ public AreaEffectCloudEntity(long entityId, long geyserId, EntityType entityType @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 8) { + } else if (entityMetadata.getId() == 9) { metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 10) { + } else if (entityMetadata.getId() == 11) { Particle particle = (Particle) entityMetadata.getValue(); - int particleId = EffectRegistry.getParticleId(particle.getType()); + int particleId = EffectRegistry.getParticleId(session, particle.getType()); if (particleId != -1) { metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index d07ecc96528..bc7ad233048 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -29,6 +29,7 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -65,7 +66,19 @@ public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { // We don't include the rotation (y) as it causes the boat to appear sideways - super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(rotation.getX() + 90, 0, rotation.getX() + 90), isOnGround, teleported); + setPosition(position.add(0d, this.entityType.getOffset(), 0d)); + setRotation(Vector3f.from(rotation.getX() + 90, 0, rotation.getX() + 90)); + setOnGround(isOnGround); + + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + // Minimal glitching when ServerVehicleMovePacket is sent + moveEntityPacket.setPosition(session.getRidingVehicleEntity() == this ? position.up(EntityType.PLAYER.getOffset() - this.entityType.getOffset()) : this.position); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround); + moveEntityPacket.setTeleported(teleported); + + session.sendUpstreamPacket(moveEntityPacket); } @Override @@ -85,26 +98,25 @@ public void updateRotation(GeyserSession session, float yaw, float pitch, boolea @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Time since last hit - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.HURT_TIME, entityMetadata.getValue()); } // Rocking direction - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); } // 'Health' in Bedrock, damage taken in Java - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { // Not exactly health but it makes motion in Bedrock metadata.put(EntityData.HEALTH, 40 - ((int) (float) entityMetadata.getValue())); } - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 11) { + } else if (entityMetadata.getId() == 12) { isPaddlingLeft = (boolean) entityMetadata.getValue(); if (isPaddlingLeft) { // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing @@ -124,7 +136,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s metadata.put(EntityData.ROW_TIME_LEFT, 0.0f); } } - else if (entityMetadata.getId() == 12) { + else if (entityMetadata.getId() == 13) { isPaddlingRight = (boolean) entityMetadata.getValue(); if (isPaddlingRight) { paddleTimeRight = 0f; @@ -139,7 +151,7 @@ else if (entityMetadata.getId() == 12) { } else { metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f); } - } else if (entityMetadata.getId() == 13) { + } else if (entityMetadata.getId() == 14) { // Possibly - I don't think this does anything? metadata.put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 52183c43162..bda4e7972b6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -46,10 +46,10 @@ public CommandBlockMinecartEntity(long entityId, long geyserId, EntityType entit @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13) { + if (entityMetadata.getId() == 14) { metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); } - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage((Component) entityMetadata.getValue())); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java index 805105c6424..24e5bb25976 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java @@ -56,7 +56,7 @@ public void spawnEntity(GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Custom block - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { customBlock = (int) entityMetadata.getValue(); if (showCustomBlock) { @@ -65,7 +65,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Custom block offset - if (entityMetadata.getId() == 11) { + if (entityMetadata.getId() == 12) { customBlockOffset = (int) entityMetadata.getValue(); if (showCustomBlock) { @@ -74,7 +74,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // If the custom block should be enabled - if (entityMetadata.getId() == 12) { + if (entityMetadata.getId() == 13) { if ((boolean) entityMetadata.getValue()) { showCustomBlock = true; metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java index 8997512d977..596ccf089a5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java @@ -47,7 +47,7 @@ public EnderCrystalEntity(long entityId, long geyserId, EntityType entityType, V public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Show beam // Usually performed client-side on Bedrock except for Ender Dragon respawn event - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { if (entityMetadata.getValue() instanceof Position) { Position pos = (Position) entityMetadata.getValue(); metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); @@ -56,7 +56,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } // There is a base located on the ender crystal - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index bc371690b14..9e5b62ca247 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -72,8 +72,6 @@ public class Entity { */ protected boolean onGround; - protected float scale = 1; - protected EntityType entityType; protected boolean valid; @@ -303,31 +301,13 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s metadata.getFlags().setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); // Triggered when crawling metadata.getFlags().setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); - float width = entityType.getWidth(); - float height = entityType.getHeight(); - switch (pose) { - case SLEEPING: - if (this instanceof LivingEntity) { - width = 0.2f; - height = 0.2f; - } - break; - case SNEAKING: - if (entityType == EntityType.PLAYER) { - height = 1.5f; - } - break; - case FALL_FLYING: - case SPIN_ATTACK: - case SWIMMING: - if (entityType == EntityType.PLAYER) { - // Seems like this is only cared about for players; nothing else - height = 0.6f; - } - break; - } - metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + setDimensions(pose); + break; + case 7: // Freezing ticks + // The value that Java edition gives us is in ticks, but Bedrock uses a float percentage of the strength 0.0 -> 1.0 + // The Java client caps its freezing tick percentage at 140 + int freezingTicks = Math.min((int) entityMetadata.getValue(), 140); + setFreezing(session, freezingTicks / 140f); break; } } @@ -345,6 +325,31 @@ public void updateBedrockMetadata(GeyserSession session) { session.sendUpstreamPacket(entityDataPacket); } + /** + * If true, the entity should be shaking on the client's end. + * + * @return whether {@link EntityFlag#SHAKING} should be set to true. + */ + protected boolean isShaking(GeyserSession session) { + return false; + } + + /** + * Set the height and width of the entity's bounding box + */ + protected void setDimensions(Pose pose) { + // No flexibility options for basic entities + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); + } + + /** + * Set a float from 0-1 - how strong the "frozen" overlay should be on screen. + */ + protected void setFreezing(GeyserSession session, float amount) { + metadata.put(EntityData.FREEZING_EFFECT_STRENGTH, amount); + } + /** * x = Pitch, y = HeadYaw, z = Yaw * diff --git a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java index 45595f5046e..f6fbcde9d6a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java @@ -32,7 +32,7 @@ public class ExpOrbEntity extends Entity { - private int amount; + private final int amount; public ExpOrbEntity(int amount, long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index deaf3fadf66..4a05dfc0380 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -41,7 +41,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FireworkColor; import org.geysermc.connector.utils.MathUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; import java.util.ArrayList; import java.util.List; @@ -55,7 +55,7 @@ public FireworkEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { ItemStack item = (ItemStack) entityMetadata.getValue(); if (item == null) { return; @@ -68,7 +68,8 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOS() == DeviceOS.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) { + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX + || session.getClientData().getDeviceOs() == DeviceOs.PS4) { return; } @@ -134,22 +135,27 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s NbtMapBuilder builder = NbtMap.builder(); builder.put("Fireworks", fireworksBuilder.build()); metadata.put(EntityData.DISPLAY_ITEM, builder.build()); - } else if (entityMetadata.getId() == 8 && !entityMetadata.getValue().equals(OptionalInt.empty()) && ((OptionalInt) entityMetadata.getValue()).getAsInt() == session.getPlayerEntity().getEntityId()) { - //Checks if the firework has an entity ID (used when a player is gliding) and checks to make sure the player that is gliding is the one getting sent the packet or else every player near the gliding player will boost too. - PlayerEntity entity = session.getPlayerEntity(); - float yaw = entity.getRotation().getX(); - float pitch = entity.getRotation().getY(); - //Uses math from NukkitX - entity.setMotion(Vector3f.from( - -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, - -Math.sin(Math.toRadians(pitch)) * 2, - Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); - //Need to update the EntityMotionPacket or else the player won't boost - SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); - entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); - entityMotionPacket.setMotion(entity.getMotion()); - - session.sendUpstreamPacket(entityMotionPacket); + } else if (entityMetadata.getId() == 9) { + OptionalInt optional = (OptionalInt) entityMetadata.getValue(); + // Checks if the firework has an entity ID (used when a player is gliding) + // and checks to make sure the player that is gliding is the one getting sent the packet + // or else every player near the gliding player will boost too. + if (optional.isPresent() && optional.getAsInt() == session.getPlayerEntity().getEntityId()) { + PlayerEntity entity = session.getPlayerEntity(); + float yaw = entity.getRotation().getX(); + float pitch = entity.getRotation().getY(); + // Uses math from NukkitX + entity.setMotion(Vector3f.from( + -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, + -Math.sin(Math.toRadians(pitch)) * 2, + Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); + // Need to update the EntityMotionPacket or else the player won't boost + SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); + entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); + entityMotionPacket.setMotion(entity.getMotion()); + + session.sendUpstreamPacket(entityMotionPacket); + } } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 0738c3819db..db44a07673f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -67,7 +67,7 @@ public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Ve @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { // Hooked entity + if (entityMetadata.getId() == 8) { // Hooked entity int hookedEntityId = (int) entityMetadata.getValue() - 1; Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId); if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) { @@ -174,11 +174,8 @@ protected float getGravity(GeyserSession session) { * @return true if this entity is currently in air. */ protected boolean isInAir(GeyserSession session) { - if (session.getConnector().getConfig().isCacheChunks()) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return block == BlockTranslator.JAVA_AIR_ID; - } - return false; + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.JAVA_AIR_ID; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java index fdf24f17605..2eb08388382 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java @@ -42,7 +42,7 @@ public FurnaceMinecartEntity(long entityId, long geyserId, EntityType entityType @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13 && !showCustomBlock) { + if (entityMetadata.getId() == 14 && !showCustomBlock) { hasFuel = (boolean) entityMetadata.getValue(); updateDefaultBlockMetadata(session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java index ed48e2670d5..1615ac8d823 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java @@ -28,6 +28,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,6 +36,8 @@ public class ItemEntity extends Entity { + protected ItemData item; + public ItemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation); } @@ -52,18 +55,28 @@ public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rota super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported); } + @Override + public void spawnEntity(GeyserSession session) { + if (item == null) { + return; + } + valid = true; + AddItemEntityPacket itemPacket = new AddItemEntityPacket(); + itemPacket.setRuntimeEntityId(geyserId); + itemPacket.setPosition(position.add(0d, this.entityType.getOffset(), 0d)); + itemPacket.setMotion(motion); + itemPacket.setUniqueEntityId(geyserId); + itemPacket.setFromFishing(false); + itemPacket.setItemInHand(item); + session.sendUpstreamPacket(itemPacket); + } + @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - AddItemEntityPacket itemPacket = new AddItemEntityPacket(); - itemPacket.setRuntimeEntityId(geyserId); - itemPacket.setPosition(position.add(0d, this.entityType.getOffset(), 0d)); - itemPacket.setMotion(motion); - itemPacket.setUniqueEntityId(geyserId); - itemPacket.setFromFishing(false); - itemPacket.getMetadata().putAll(metadata); - itemPacket.setItemInHand(ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue())); - session.sendUpstreamPacket(itemPacket); + if (entityMetadata.getId() == 8) { + item = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); + despawnEntity(session); + spawnEntity(session); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index e10ad0afd69..f2f36e606da 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import lombok.Getter; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -69,6 +70,11 @@ public class ItemFrameEntity extends Entity { * Cached item frame's Bedrock compound tag. */ private NbtMap cachedTag; + /** + * The item currently in the item frame. Used for block picking. + */ + @Getter + private ItemStack heldItem = null; public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -78,7 +84,7 @@ public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vect @Override public void spawnEntity(GeyserSession session) { NbtMapBuilder blockBuilder = NbtMap.builder() - .putString("name", "minecraft:frame") + .putString("name", this.entityType == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_frame" : "minecraft:frame") .putInt("version", session.getBlockTranslator().getBlockStateVersion()); blockBuilder.put("states", NbtMap.builder() .putInt("facing_direction", direction.ordinal()) @@ -87,7 +93,8 @@ public void spawnEntity(GeyserSession session) { bedrockRuntimeId = session.getBlockTranslator().getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); - session.getItemFrameCache().put(bedrockPosition, entityId); + session.getItemFrameCache().put(bedrockPosition, this); + // Delay is required, or else loading in frames on chunk load is sketchy at best session.getConnector().getGeneralThreadPool().schedule(() -> { updateBlock(session); @@ -98,14 +105,15 @@ public void spawnEntity(GeyserSession session) { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { - ItemData itemData = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); + if (entityMetadata.getId() == 8 && entityMetadata.getValue() != null) { + this.heldItem = (ItemStack) entityMetadata.getValue(); + ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem); ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { - builder.put("tag", itemData.getTag().toBuilder().build()); + builder.put("tag", itemData.getTag()); } builder.putShort("Damage", (short) itemData.getDamage()); builder.putString("Name", itemEntry.getBedrockIdentifier()); @@ -116,11 +124,11 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s cachedTag = tag.build(); updateBlock(session); } - else if (entityMetadata.getId() == 7 && entityMetadata.getValue() == null && cachedTag != null) { + else if (entityMetadata.getId() == 8 && entityMetadata.getValue() == null && cachedTag != null) { cachedTag = getDefaultTag(); updateBlock(session); } - else if (entityMetadata.getId() == 8) { + else if (entityMetadata.getId() == 9) { rotation = ((int) entityMetadata.getValue()) * 45; if (cachedTag == null) { updateBlock(session); @@ -146,7 +154,9 @@ public boolean despawnEntity(GeyserSession session) { updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.sendUpstreamPacket(updateBlockPacket); - session.getItemFrameCache().remove(position, entityId); + + session.getItemFrameCache().remove(bedrockPosition, this); + valid = false; return true; } @@ -157,7 +167,7 @@ private NbtMap getDefaultTag() { builder.putInt("y", bedrockPosition.getY()); builder.putInt("z", bedrockPosition.getZ()); builder.putByte("isMovable", (byte) 1); - builder.putString("id", "ItemFrame"); + builder.putString("id", this.entityType == EntityType.GLOW_ITEM_FRAME ? "GlowItemFrame" : "ItemFrame"); return builder.build(); } @@ -192,16 +202,7 @@ public void updateBlock(GeyserSession session) { * @param session GeyserSession. * @return Java entity ID or -1 if not found. */ - public static long getItemFrameEntityId(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().getOrDefault(position, -1); - } - - /** - * Force-remove from the position-to-ID map so it doesn't cause conflicts. - * @param session GeyserSession. - * @param position position of the removed item frame. - */ - public static void removePosition(GeyserSession session, Vector3i position) { - session.getItemFrameCache().remove(position); + public static ItemFrameEntity getItemFrameEntity(GeyserSession session, Vector3i position) { + return session.getItemFrameCache().get(position); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index 025cf085b72..4ffb626a85b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -26,6 +26,7 @@ package org.geysermc.connector.entity; 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; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -61,6 +62,11 @@ public class LivingEntity extends Entity { protected ItemData hand = ItemData.AIR; protected ItemData offHand = ItemData.AIR; + /** + * A convenience variable for if the entity has reached the maximum frozen ticks and should be shaking + */ + private boolean isMaxFrozenState = false; + public LivingEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -68,7 +74,7 @@ public LivingEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { switch (entityMetadata.getId()) { - case 7: // blocking + case 8: // blocking byte xd = (byte) entityMetadata.getValue(); //blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like @@ -81,24 +87,22 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s // Riptide spin attack metadata.getFlags().setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); break; - case 8: + case 9: metadata.put(EntityData.HEALTH, entityMetadata.getValue()); break; - case 9: + case 10: metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); break; - case 10: + case 11: metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); break; - case 13: // Bed Position + case 14: // Bed Position Position bedPosition = (Position) entityMetadata.getValue(); if (bedPosition != null) { metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, bedPosition); - } + int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); // Indicate that the player should enter the sleep cycle // Has to be a byte or it does not work // (Bed position is what actually triggers sleep - "pose" is only optional) @@ -113,6 +117,28 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); } + @Override + protected boolean isShaking(GeyserSession session) { + return isMaxFrozenState; + } + + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.SLEEPING) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); + } else { + super.setDimensions(pose); + } + } + + @Override + protected void setFreezing(GeyserSession session, float amount) { + super.setFreezing(session, amount); + this.isMaxFrozenState = amount >= 1.0f; + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); + } + public void updateAllEquipment(GeyserSession session) { if (!valid) return; diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java index 49b12a3e1b4..18d1d92a13b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -40,33 +40,33 @@ public MinecartEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { metadata.put(EntityData.HEALTH, entityMetadata.getValue()); } // Direction in which the minecart is shaking - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); } // Power in Java, time in Bedrock - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { metadata.put(EntityData.HURT_TIME, Math.min((int) (float) entityMetadata.getValue(), 15)); } if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class // Custom block - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // Custom block offset - if (entityMetadata.getId() == 11) { + if (entityMetadata.getId() == 12) { metadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getValue()); } // If the custom block should be enabled - if (entityMetadata.getId() == 12) { + if (entityMetadata.getId() == 13) { // Needs a byte based off of Java's boolean metadata.put(EntityData.CUSTOM_DISPLAY, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); } @@ -79,4 +79,10 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported); } + + @Override + public Vector3f getBedrockRotation() { + // Note: minecart rotation on rails does not care about the actual rotation value + return Vector3f.from(0, rotation.getX(), 0); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java index ad73c1d9c4d..1d70d9d5f10 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java @@ -45,7 +45,7 @@ public TNTEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { currentTick = (int) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IGNITED, true); metadata.put(EntityData.FUSE_LENGTH, currentTick); diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index 1088b2a0be6..1ba18bfd727 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -166,13 +166,8 @@ protected float getDrag(GeyserSession session) { * @return true if this entity is currently in water. */ protected boolean isInWater(GeyserSession session) { - if (session.getConnector().getConfig().isCacheChunks()) { - if (0 <= position.getFloorY() && position.getFloorY() <= 255) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return BlockStateValues.getWaterLevel(block) != -1; - } - } - return false; + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return BlockStateValues.getWaterLevel(block) != -1; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java index c1f82836b02..9ce218a8109 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrownPotionEntity.java @@ -51,7 +51,7 @@ public ThrownPotionEntity(long entityId, long geyserId, EntityType entityType, V @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7 && entityMetadata.getType() == MetadataType.ITEM) { + if (entityMetadata.getId() == 8 && entityMetadata.getType() == MetadataType.ITEM) { ItemStack itemStack = (ItemStack) entityMetadata.getValue(); ItemEntry itemEntry = ItemRegistry.getItem(itemStack); if (itemEntry.getJavaIdentifier().endsWith("potion") && itemStack.getNbt() != null) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java index 714a70daa0c..f7b63435f3d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -44,17 +44,17 @@ public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Ve @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Arrow potion effect color - if (entityMetadata.getId() == 9) { + if (entityMetadata.getId() == 10) { int potionColor = (int) entityMetadata.getValue(); // -1 means no color if (potionColor == -1) { - metadata.remove(EntityData.CUSTOM_DISPLAY); + metadata.put(EntityData.CUSTOM_DISPLAY, 0); } else { TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); if (potion != null && potion.getJavaColor() != -1) { metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); } else { - metadata.remove(EntityData.CUSTOM_DISPLAY); + metadata.put(EntityData.CUSTOM_DISPLAY, 0); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java index 014c0049eb0..19ec276924a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java @@ -39,7 +39,7 @@ public TridentEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 10) { + if (entityMetadata.getId() == 11) { metadata.getFlags().setFlag(EntityFlag.ENCHANTED, (boolean) entityMetadata.getValue()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java index 3548e0dfe34..6209538dce3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -46,7 +46,7 @@ protected float getDrag(GeyserSession session) { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { + if (entityMetadata.getId() == 8) { boolean newIsCharged = (boolean) entityMetadata.getValue(); if (newIsCharged != isCharged) { isCharged = newIsCharged; diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java index 2a2d47ba986..9cb80389841 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java @@ -28,10 +28,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter @AllArgsConstructor +@ToString public class Attribute { private AttributeType type; diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java index ccd0bcb5bc5..6877bb7c6fd 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java @@ -57,12 +57,12 @@ public enum AttributeType { HUNGER(null, "minecraft:player.hunger", 0f, 20f, 20f), SATURATION(null, "minecraft:player.saturation", 0f, 20f, 20f); - private String javaIdentifier; - private String bedrockIdentifier; + private final String javaIdentifier; + private final String bedrockIdentifier; - private float minimum; - private float maximum; - private float defaultValue; + private final float minimum; + private final float maximum; + private final float defaultValue; public Attribute getAttribute(float value) { return getAttribute(value, maximum); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java index 909cb736baf..8f8166d07a8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java @@ -40,7 +40,7 @@ public AgeableEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); metadata.put(EntityData.SCALE, isBaby ? .55f : 1f); metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 3d100551023..a616f926927 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -27,6 +27,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -125,7 +126,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } else if (entityMetadata.getId() == 2) { updateSecondEntityStatus(false); - } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { + } else if (entityMetadata.getId() == 15 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall @@ -157,6 +158,72 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s updateSecondEntityStatus(false); } + + // The following values don't do anything on normal Bedrock. + // But if given a resource pack, then we can use these values to control armor stand visual properties + metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x04) != 0x04); // Has arms + metadata.getFlags().setFlag(EntityFlag.ADMIRING, (xd & 0x08) == 0x08); // Has no baseplate + } else { + EntityData dataLeech = null; + EntityFlag negativeXToggle = null; + EntityFlag negativeYToggle = null; + EntityFlag negativeZToggle = null; + switch (entityMetadata.getId()) { + case 16: // Head + dataLeech = EntityData.MARK_VARIANT; + negativeXToggle = EntityFlag.INTERESTED; + negativeYToggle = EntityFlag.CHARGED; + negativeZToggle = EntityFlag.POWERED; + break; + case 17: // Body + dataLeech = EntityData.VARIANT; + negativeXToggle = EntityFlag.IN_LOVE; + negativeYToggle = EntityFlag.CELEBRATING; + negativeZToggle = EntityFlag.CELEBRATING_SPECIAL; + break; + case 18: // Left arm + dataLeech = EntityData.TRADE_TIER; + negativeXToggle = EntityFlag.CHARGING; + negativeYToggle = EntityFlag.CRITICAL; + negativeZToggle = EntityFlag.DANCING; + break; + case 19: // Right arm + dataLeech = EntityData.MAX_TRADE_TIER; + negativeXToggle = EntityFlag.ELDER; + negativeYToggle = EntityFlag.EMOTING; + negativeZToggle = EntityFlag.IDLING; + break; + case 20: // Left leg + dataLeech = EntityData.SKIN_ID; + negativeXToggle = EntityFlag.IS_ILLAGER_CAPTAIN; + negativeYToggle = EntityFlag.IS_IN_UI; + negativeZToggle = EntityFlag.LINGERING; + break; + case 21: // Right leg + dataLeech = EntityData.HURT_DIRECTION; + negativeXToggle = EntityFlag.IS_PREGNANT; + negativeYToggle = EntityFlag.SHEARED; + negativeZToggle = EntityFlag.STALKING; + break; + } + if (dataLeech != null) { + // Indicate that rotation should be checked + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + + Rotation rotation = (Rotation) entityMetadata.getValue(); + int rotationX = getRotation(rotation.getPitch()); + int rotationY = getRotation(rotation.getYaw()); + int rotationZ = getRotation(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); + int value = (topBit * 1000000) + ((Math.abs(rotationX) % 100) * 10000) + ((Math.abs(rotationY) % 100) * 100) + (Math.abs(rotationZ) % 100); + metadata.put(dataLeech, value); + // Set the entity flags if a value is negative + metadata.getFlags().setFlag(negativeXToggle, rotationX < 0); + metadata.getFlags().setFlag(negativeYToggle, rotationY < 0); + metadata.getFlags().setFlag(negativeZToggle, rotationZ < 0); + } } if (secondEntity != null) { secondEntity.updateBedrockMetadata(entityMetadata, session); @@ -302,6 +369,17 @@ private void updateSecondEntityStatus(boolean sendMetadata) { } } + 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. */ diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java index 80b426b42f6..110c02e06c1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java @@ -39,7 +39,7 @@ public BatEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java new file mode 100644 index 00000000000..b68c5d2242e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/GlowSquidEntity.java @@ -0,0 +1,43 @@ +/* + * 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.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GlowSquidEntity extends SquidEntity { + public GlowSquidEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + // TODO "dark ticks remaining" ??? does this have a Bedrock equivalent? + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java index 90bb373fe26..d0f99cdb979 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java @@ -41,7 +41,7 @@ public InsentientEntity(long entityId, long geyserId, EntityType entityType, Vec @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { + if (entityMetadata.getId() == 15 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java new file mode 100644 index 00000000000..f47ff2c7f77 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java @@ -0,0 +1,80 @@ +/* + * 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.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class IronGolemEntity extends GolemEntity { + + public IronGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that we should show cracks through a resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + // Required, or else the overlay is black + metadata.put(EntityData.COLOR_2, (byte) 0); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 9) { + // Required so the resource pack sees the entity health + attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(metadata.getFloat(EntityData.HEALTH), 100f)); + updateBedrockAttributes(session); + } + } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java index da088410a92..f08fcc796ae 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java @@ -39,7 +39,7 @@ public SlimeEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { this.metadata.put(EntityData.SCALE, 0.10f + (int) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java index 144b0ed2441..6bfb2356460 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java @@ -39,7 +39,7 @@ public SnowGolemEntity(long entityId, long geyserId, EntityType entityType, Vect @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); // Handle the visibility of the pumpkin metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java index d4a4092421f..df914162d91 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java @@ -29,7 +29,6 @@ import org.geysermc.connector.entity.type.EntityType; public class SquidEntity extends WaterEntity { - public SquidEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java index fc5bc722c94..efded1f6ff5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java @@ -28,10 +28,22 @@ import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.living.AgeableEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class AnimalEntity extends AgeableEntity { public AnimalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + /** + * @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example, + * wheat. + * @return true if this is a valid item to breed with for this animal. + */ + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + // This is what it defaults to. OK. + return javaIdentifierStripped.equals("wheat"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java new file mode 100644 index 00000000000..7c6c81ba23c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AxolotlEntity.java @@ -0,0 +1,65 @@ +/* + * 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.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +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.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; + +public class AxolotlEntity extends AnimalEntity { + public AxolotlEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 17) { + int variant = (int) entityMetadata.getValue(); + switch (variant) { + case 1: // Java - "Wild" (brown) + variant = 3; + break; + case 3: // Java - cyan + variant = 1; + break; + } + metadata.put(EntityData.VARIANT, variant); + } + else if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.PLAYING_DEAD, (boolean) entityMetadata.getValue()); + } + } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("tropical_fish_bucket"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index bdffbbcd5af..59035bb8522 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class BeeEntity extends AnimalEntity { @@ -42,7 +43,7 @@ public BeeEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); // Bee is performing sting attack; trigger animation if ((xd & 0x02) == 0x02) { @@ -57,10 +58,15 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s // If the bee has nectar or not metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { // Converting "anger time" to a boolean metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return session.getTagCache().isFlower(itemEntry); + } } diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java similarity index 63% rename from common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java rename to connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java index e1fbdbd0d2c..b5f0395d476 100644 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java @@ -23,35 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.connector.entity.living.animal; -import lombok.Getter; -import lombok.Setter; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; -import java.util.ArrayList; -import java.util.List; +public class ChickenEntity extends AnimalEntity { -public class DropdownComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private List options = new ArrayList<>(); - - @Getter - @Setter - private int defaultOptionIndex; - - public DropdownComponent() { - super("dropdown"); + public ChickenEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); } - public void addOption(String option, boolean isDefault) { - options.add(option); - if (isDefault) - defaultOptionIndex = options.size() - 1; + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.contains("seeds"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 90514cf1239..2c193413940 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class FoxEntity extends AnimalEntity { @@ -40,10 +41,10 @@ public FoxEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); @@ -52,4 +53,9 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return session.getTagCache().isFoxFood(itemEntry); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java new file mode 100644 index 00000000000..a43998f2794 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/GoatEntity.java @@ -0,0 +1,65 @@ +/* + * 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.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import lombok.Getter; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +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(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 17) { + // Not used in Bedrock Edition + isScreamer = (boolean) entityMetadata.getValue(); + } + } + + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.LONG_JUMPING) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, LONG_JUMPING_WIDTH); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, LONG_JUMPING_HEIGHT); + } else { + super.setDimensions(pose); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java index 1878648b4f2..492ff68a8b3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -30,9 +30,11 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.utils.DimensionUtils; public class HoglinEntity extends AnimalEntity { + private boolean isImmuneToZombification; public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -40,11 +42,22 @@ public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { // Immune to zombification? // Apply shaking effect if not in the nether and zombification is possible - metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + this.isImmuneToZombification = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + protected boolean isShaking(GeyserSession session) { + return (!isImmuneToZombification && !session.getDimension().equals(DimensionUtils.NETHER)) || super.isShaking(session); + } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("crimson_fungus"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java index 9e12f3f1eea..e2e22c73d51 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java @@ -39,7 +39,7 @@ public MooshroomEntity(long entityId, long geyserId, EntityType entityType, Vect @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java index 87320838ae1..48a5e08bfdc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class OcelotEntity extends AnimalEntity { @@ -39,9 +40,14 @@ public OcelotEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.TRUSTING, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index eec07af5099..83f0ed8d912 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; public class PandaEntity extends AnimalEntity { @@ -46,7 +47,7 @@ public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); if ((int) entityMetadata.getValue() != 0) { @@ -58,15 +59,15 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s session.sendUpstreamPacket(packet); } } - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { mainGene = (int) (byte) entityMetadata.getValue(); updateAppearance(); } - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { hiddenGene = (int) (byte) entityMetadata.getValue(); updateAppearance(); } - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 22) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); @@ -79,6 +80,11 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); } + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("bamboo"); + } + /** * 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 diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java index e747405b227..27d736b33a5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class PigEntity extends AnimalEntity { @@ -39,13 +40,17 @@ public PigEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); + } + @Override protected float getDefaultMaxHealth() { return 10f; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java index db658dd8eb6..8f292411de2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class PolarBearEntity extends AnimalEntity { @@ -39,9 +40,14 @@ public PolarBearEntity(long entityId, long geyserId, EntityType entityType, Vect @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.STANDING, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return false; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java index 9a6a712f98c..0ddd581f850 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java @@ -40,10 +40,9 @@ public PufferFishEntity(long entityId, long geyserId, EntityType entityType, Vec @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - // Transfers correctly but doesn't apply on the client + if (entityMetadata.getId() == 17) { int puffsize = (int) entityMetadata.getValue(); - metadata.put(EntityData.PUFFERFISH_SIZE, puffsize); + metadata.put(EntityData.PUFFERFISH_SIZE, (byte) puffsize); metadata.put(EntityData.VARIANT, puffsize); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 752a0d1062f..41579d6a67b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class RabbitEntity extends AnimalEntity { @@ -41,22 +42,30 @@ public RabbitEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.SCALE, .55f); boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } - } else if (entityMetadata.getId() == 16) { + } else if (entityMetadata.getId() == 17) { int variant = (int) entityMetadata.getValue(); // Change the killer bunny to display as white since it only exists on Java Edition - if (variant == 99) { + boolean isKillerBunny = variant == 99; + if (isKillerBunny) { variant = 1; } + // Allow the resource pack to adjust to the killer bunny + metadata.getFlags().setFlag(EntityFlag.BRIBED, isKillerBunny); metadata.put(EntityData.VARIANT, variant); } } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot"); + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java index 37bb2fdeb9d..f723eff2b0f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java @@ -40,7 +40,7 @@ public SheepEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); metadata.put(EntityData.COLOR, xd); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index 9ea97eb18c1..437f60cb440 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -31,10 +31,11 @@ import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class StriderEntity extends AnimalEntity { - private boolean shaking = false; + private boolean isCold = false; public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -45,10 +46,10 @@ public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { - shaking = (boolean) entityMetadata.getValue(); - } if (entityMetadata.getId() == 18) { + isCold = (boolean) entityMetadata.getValue(); + } + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); } @@ -71,8 +72,8 @@ public void updateBedrockMetadata(GeyserSession session) { metadata.getFlags().setFlag(EntityFlag.BREATHING, !parentShaking); metadata.getFlags().setFlag(EntityFlag.SHAKING, parentShaking); } else { - metadata.getFlags().setFlag(EntityFlag.BREATHING, !shaking); - metadata.getFlags().setFlag(EntityFlag.SHAKING, shaking); + metadata.getFlags().setFlag(EntityFlag.BREATHING, !isCold); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } // Update the passengers if we have any @@ -85,4 +86,14 @@ public void updateBedrockMetadata(GeyserSession session) { super.updateBedrockMetadata(session); } + + @Override + protected boolean isShaking(GeyserSession session) { + return isCold || super.isShaking(session); + } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("warped_fungus"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java index 8d5b476a0de..7a5906dd9df 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java @@ -28,8 +28,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import lombok.AllArgsConstructor; -import lombok.Getter; import org.geysermc.connector.entity.living.AbstractFishEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -42,34 +40,14 @@ public TropicalFishEntity(long entityId, long geyserId, EntityType entityType, V @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - TropicalFishVariant variant = TropicalFishVariant.fromVariantNumber((int) entityMetadata.getValue()); + if (entityMetadata.getId() == 17) { + int varNumber = (int) entityMetadata.getValue(); - metadata.put(EntityData.VARIANT, variant.getShape()); // Shape 0-1 - metadata.put(EntityData.MARK_VARIANT, variant.getPattern()); // Pattern 0-5 - metadata.put(EntityData.COLOR, variant.getBaseColor()); // Base color 0-15 - metadata.put(EntityData.COLOR_2, variant.getPatternColor()); // Pattern color 0-15 + metadata.put(EntityData.VARIANT, varNumber & 0xFF); // Shape 0-1 + metadata.put(EntityData.MARK_VARIANT, (varNumber >> 8) & 0xFF); // Pattern 0-5 + metadata.put(EntityData.COLOR, (byte) ((varNumber >> 16) & 0xFF)); // Base color 0-15 + metadata.put(EntityData.COLOR_2, (byte) ((varNumber >> 24) & 0xFF)); // Pattern color 0-15 } super.updateBedrockMetadata(entityMetadata, session); } - - @Getter - @AllArgsConstructor - private static class TropicalFishVariant { - private int shape; - private int pattern; - private byte baseColor; - private byte patternColor; - - /** - * Convert the variant number from Java into separate values - * - * @param varNumber Variant number from Java edition - * - * @return The variant converted into TropicalFishVariant - */ - public static TropicalFishVariant fromVariantNumber(int varNumber) { - return new TropicalFishVariant((varNumber & 0xFF), ((varNumber >> 8) & 0xFF), (byte) ((varNumber >> 16) & 0xFF), (byte) ((varNumber >> 24) & 0xFF)); - } - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java index 9456f4d2880..7e9e3260d66 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class TurtleEntity extends AnimalEntity { @@ -39,11 +40,16 @@ public TurtleEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 18) { + } else if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("seagrass"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index 1fe8d4362ce..5f4082e5059 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -26,6 +26,7 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.google.common.collect.ImmutableSet; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; @@ -36,9 +37,18 @@ import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; +import java.util.Set; + public class AbstractHorseEntity extends AnimalEntity { + /** + * A list of all foods a horse/donkey can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final Set DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -54,7 +64,7 @@ public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); @@ -96,9 +106,14 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 8) { + if (entityMetadata.getId() == 9) { // Update the health attribute updateBedrockAttributes(session); } } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java index 461d636bd19..e4f0cc24188 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java @@ -42,7 +42,7 @@ public ChestedHorseEntity(long entityId, long geyserId, EntityType entityType, V @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.CHESTED, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index c687898d0cb..094726de1d9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -39,7 +39,7 @@ public HorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index 48e321932cc..67e3304d3c3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; public class LlamaEntity extends ChestedHorseEntity { @@ -45,11 +46,11 @@ public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Strength - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { metadata.put(EntityData.STRENGTH, entityMetadata.getValue()); } // Color equipped on the llama - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { // Bedrock treats llama decoration as armor MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); equipmentPacket.setRuntimeEntityId(geyserId); @@ -70,9 +71,14 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s session.sendUpstreamPacket(equipmentPacket); } // Color of the llama - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 22) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 87d7002592f..ac4142988f3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class CatEntity extends TameableEntity { @@ -49,12 +50,14 @@ public void updateRotation(GeyserSession session, float yaw, float pitch, boolea public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { super.updateBedrockMetadata(entityMetadata, session); if (entityMetadata.getId() == 16) { + metadata.put(EntityData.SCALE, (boolean) entityMetadata.getValue() ? 0.4f : 0.8f); + } else if (entityMetadata.getId() == 17) { // Update collar color if tamed if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { metadata.put(EntityData.COLOR, collarColor); } } - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { // Different colors in Java and Bedrock for some reason int variantColor; switch ((int) entityMetadata.getValue()) { @@ -75,7 +78,10 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } metadata.put(EntityData.VARIANT, variantColor); } - if (entityMetadata.getId() == 21) { + if (entityMetadata.getId() == 20) { + metadata.getFlags().setFlag(EntityFlag.RESTING, (boolean) entityMetadata.getValue()); + } + if (entityMetadata.getId() == 22) { collarColor = (byte) (int) entityMetadata.getValue(); // Needed or else wild cats are a red color if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { @@ -83,4 +89,9 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java index f9df03d6ba4..dcc9d6f785d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class ParrotEntity extends TameableEntity { @@ -40,9 +41,14 @@ public ParrotEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Parrot color - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 21bf0a1b729..923e137124a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -44,7 +44,7 @@ public TameableEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); @@ -52,7 +52,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Note: Must be set for wolf collar color to work - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { if (entityMetadata.getValue() != null) { // Owner UUID of entity Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index 144c0fe2566..0a5d2a58c4f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -26,13 +26,24 @@ package org.geysermc.connector.entity.living.animal.tameable; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.google.common.collect.ImmutableSet; 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.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; + +import java.util.Set; public class WolfEntity extends TameableEntity { + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag or particles if needed. + */ + private static final Set WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); private byte collarColor; @@ -43,7 +54,7 @@ public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { //Reset wolf color - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); boolean angry = (xd & 0x02) == 0x02; if (angry) { @@ -52,13 +63,13 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue()); } // Wolf collar color // Relies on EntityData.OWNER_EID being set in TameableEntity.java - if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { + if (entityMetadata.getId() == 20 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue()); if (!metadata.containsKey(EntityData.OWNER_EID)) { // If a color is set and there is no owner entity ID, set one. @@ -68,11 +79,17 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Wolf anger (1.16+) - if (entityMetadata.getId() == 20) { + if (entityMetadata.getId() == 21) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0); metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + // Cannot be a baby to eat these foods + return WOLF_FOODS.contains(javaIdentifierStripped) && !metadata.getFlags().getFlag(EntityFlag.BABY); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index fa5785fe51b..1ba8b595b5d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -46,7 +46,7 @@ public class VillagerEntity extends AbstractMerchantEntity { /** * A map of Java profession IDs to Bedrock IDs */ - private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); + public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap(); /** * A map of all Java region IDs (plains, savanna...) to Bedrock */ @@ -54,21 +54,21 @@ public class VillagerEntity extends AbstractMerchantEntity { static { // Java villager profession IDs -> Bedrock - VILLAGER_VARIANTS.put(0, 0); - VILLAGER_VARIANTS.put(1, 8); - VILLAGER_VARIANTS.put(2, 11); - VILLAGER_VARIANTS.put(3, 6); - VILLAGER_VARIANTS.put(4, 7); - VILLAGER_VARIANTS.put(5, 1); - VILLAGER_VARIANTS.put(6, 2); - VILLAGER_VARIANTS.put(7, 4); - VILLAGER_VARIANTS.put(8, 12); - VILLAGER_VARIANTS.put(9, 5); - VILLAGER_VARIANTS.put(10, 13); - VILLAGER_VARIANTS.put(11, 14); - VILLAGER_VARIANTS.put(12, 3); - VILLAGER_VARIANTS.put(13, 10); - VILLAGER_VARIANTS.put(14, 9); + 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_REGIONS.put(0, 1); VILLAGER_REGIONS.put(1, 2); @@ -85,10 +85,10 @@ public VillagerEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { VillagerData villagerData = (VillagerData) entityMetadata.getValue(); // Profession - metadata.put(EntityData.VARIANT, VILLAGER_VARIANTS.get(villagerData.getProfession())); + metadata.put(EntityData.VARIANT, VILLAGER_PROFESSIONS.get(villagerData.getProfession())); //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? // Region metadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); @@ -111,7 +111,7 @@ public void moveRelative(GeyserSession session, double relX, double relY, double float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION, null); - if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { + if (bedPosition != null) { bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java index cd07faf393d..bc17e27ca0a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java @@ -39,10 +39,10 @@ public AbstractSkeletonEntity(long entityId, long geyserId, EntityType entityTyp @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { byte xd = (byte) entityMetadata.getValue(); // A bit of a loophole so the hands get raised - set the target ID to its own ID - metadata.put(EntityData.TARGET_EID, (xd == 4) ? geyserId : 0); + metadata.put(EntityData.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java index 0dac9207726..cccdaf7d23c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java @@ -33,6 +33,7 @@ import org.geysermc.connector.utils.DimensionUtils; public class BasePiglinEntity extends MonsterEntity { + private boolean isImmuneToZombification; public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -40,11 +41,17 @@ public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vec @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { // Immune to zombification? // Apply shaking effect if not in the nether and zombification is possible - metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + this.isImmuneToZombification = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + protected boolean isShaking(GeyserSession session) { + return (!isImmuneToZombification && !session.getDimension().equals(DimensionUtils.NETHER)) || super.isShaking(session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java index dcbb3935049..6e1bdce5354 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java @@ -39,7 +39,7 @@ public BlazeEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.ON_FIRE, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index b62337ec2f4..a1dc0282126 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -45,15 +45,15 @@ public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { if (!ignitedByFlintAndSteel) { metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); } } - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index 6216797982d..74bd5b7ce26 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -92,7 +92,7 @@ public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Ve @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { // Phase + if (entityMetadata.getId() == 16) { // Phase phase = (int) entityMetadata.getValue(); phaseTicks = 0; metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting()); @@ -100,7 +100,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 8) { // Health + if (entityMetadata.getId() == 9) { // Health // Update the health attribute, so that the death animation gets played // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1 float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH)); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java index 0d265b56e61..f11e57a8fee 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -43,11 +43,11 @@ public EndermanEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Held block - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.CARRIED_BLOCK, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // "Is screaming" - controls sound - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { if ((boolean) entityMetadata.getValue()) { LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet(); packet.setSound(SoundEvent.STARE); @@ -58,7 +58,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } // "Is staring/provoked" - controls visuals - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java index 69bb384a663..22d91ec3665 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java @@ -40,7 +40,7 @@ public GhastEntity(long entityId, long geyserId, EntityType entityType, Vector3f @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { // If the ghast is attacking metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java index d254a329974..0b28cd53ec4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java @@ -40,7 +40,7 @@ public GuardianEntity(long entityId, long geyserId, EntityType entityType, Vecto @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { entity = session.getPlayerEntity(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PhantomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PhantomEntity.java new file mode 100644 index 00000000000..bfa1ea7247b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PhantomEntity.java @@ -0,0 +1,53 @@ +/* + * 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.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.living.FlyingEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class PhantomEntity extends FlyingEntity { + public PhantomEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { // Size + int size = (int) entityMetadata.getValue(); + float modelScale = 1f + 0.15f * size; + float boundsScale = (1f + (0.2f * size) / EntityType.PHANTOM.getWidth()) / modelScale; + + metadata.put(EntityData.BOUNDING_BOX_WIDTH, boundsScale * EntityType.PHANTOM.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, boundsScale * EntityType.PHANTOM.getHeight()); + metadata.put(EntityData.SCALE, modelScale); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index e6e509b11c4..5b1ccd34224 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -41,17 +41,17 @@ public PiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { + if (entityMetadata.getId() == 17) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } } - if (entityMetadata.getId() == 17) { + if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.CHARGING, (boolean) entityMetadata.getValue()); } - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { metadata.getFlags().setFlag(EntityFlag.DANCING, (boolean) entityMetadata.getValue()); } @@ -61,7 +61,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s @Override public void updateOffHand(GeyserSession session) { // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly so its pose updates - boolean changed = metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD.getBedrockId()); + boolean changed = metadata.getFlags().setFlag(EntityFlag.ADMIRING, session.getTagCache().shouldPiglinAdmire(ItemRegistry.getItem(this.offHand))); if (changed) { super.updateBedrockMetadata(session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java index f31dde69c4d..142c0012ba8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -26,11 +26,10 @@ package org.geysermc.connector.entity.living.monster; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; 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.entity.EntityFlag; import org.geysermc.connector.entity.living.GolemEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,20 +38,16 @@ public class ShulkerEntity extends GolemEntity { public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that invisibility should be fixed through the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { BlockFace blockFace = (BlockFace) entityMetadata.getValue(); metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); } - if (entityMetadata.getId() == 16) { - Position position = (Position) entityMetadata.getValue(); - if (position != null) { - metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); - } - } if (entityMetadata.getId() == 17) { int height = (byte) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SkeletonEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java rename to connector/src/main/java/org/geysermc/connector/entity/living/monster/SkeletonEntity.java index e10a503ea3d..44c1d1f8577 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SkeletonEntity.java @@ -23,37 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block; +package org.geysermc.connector.entity.living.monster; -import com.google.common.collect.ImmutableSet; -import com.nukkitx.nbt.NbtMapBuilder; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -import java.util.Set; +public class SkeletonEntity extends AbstractSkeletonEntity { + private boolean convertingToStray = false; -public class BlockTranslator1_16_100 extends BlockTranslator { - private static final Set CORRECTED_STATES = ImmutableSet.of("minecraft:stripped_warped_stem", - "minecraft:stripped_warped_hyphae", "minecraft:stripped_crimson_stem", "minecraft:stripped_crimson_hyphae"); - - public static final BlockTranslator1_16_100 INSTANCE = new BlockTranslator1_16_100(); - - public BlockTranslator1_16_100() { - super("bedrock/blockpalette.1_16_100.nbt"); - } - - @Override - public int getBlockStateVersion() { - return 17825808; + public SkeletonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); } @Override - protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { - if (CORRECTED_STATES.contains(bedrockIdentifier)) { - statesBuilder.putInt("deprecated", 0); + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 16) { + this.convertingToStray = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } - return super.adjustBlockStateForVersion(bedrockIdentifier, statesBuilder); } - public static void init() { - // no-op + @Override + protected boolean isShaking(GeyserSession session) { + return convertingToStray; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java index 5706c1d6969..65c30289a46 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java @@ -39,7 +39,7 @@ public SpiderEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.WALL_CLIMBING, (xd & 0x01) == 0x01); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java index cbf0c149a9e..990a2f3a9db 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java @@ -39,7 +39,7 @@ public VexEntity(long entityId, long geyserId, EntityType entityType, Vector3f p @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); // Set the target to the player to force the attack animation // even if the player isn't the target as we dont get the target on Java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java index e024b4e55a8..d6d7f80744b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java @@ -44,7 +44,7 @@ public WitherEntity(long entityId, long geyserId, EntityType entityType, Vector3 public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { long targetID = 0; - if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) { + if (entityMetadata.getId() >= 16 && entityMetadata.getId() <= 18) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); if (entity == null && session.getPlayerEntity().getEntityId() == (int) entityMetadata.getValue()) { entity = session.getPlayerEntity(); @@ -55,13 +55,13 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { metadata.put(EntityData.WITHER_TARGET_1, targetID); - } else if (entityMetadata.getId() == 16) { - metadata.put(EntityData.WITHER_TARGET_2, targetID); } else if (entityMetadata.getId() == 17) { - metadata.put(EntityData.WITHER_TARGET_3, targetID); + metadata.put(EntityData.WITHER_TARGET_2, targetID); } else if (entityMetadata.getId() == 18) { + metadata.put(EntityData.WITHER_TARGET_3, targetID); + } else if (entityMetadata.getId() == 19) { metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, entityMetadata.getValue()); // Show the shield for the first few seconds of spawning (like Java) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java index 585a1e2ca66..dde19927da7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java @@ -40,7 +40,7 @@ public ZoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java index f3e0fdad817..bae593e4fef 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java @@ -33,6 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ZombieEntity extends MonsterEntity { + private boolean convertingToDrowned = false; public ZombieEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -40,13 +41,19 @@ public ZombieEntity(long entityId, long geyserId, EntityType entityType, Vector3 @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); - if (isBaby) { - metadata.put(EntityData.SCALE, .55f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } + metadata.put(EntityData.SCALE, isBaby ? .55f : 1.0f); + metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); + } else if (entityMetadata.getId() == 18) { + convertingToDrowned = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + protected boolean isShaking(GeyserSession session) { + return convertingToDrowned || super.isShaking(session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java index c098fb5f680..2e3308ec128 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java @@ -35,6 +35,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ZombieVillagerEntity extends ZombieEntity { + private boolean isTransforming; public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -42,15 +43,23 @@ public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { + isTransforming = (boolean) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); - metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 20) { VillagerData villagerData = (VillagerData) entityMetadata.getValue(); - // Region - only one used on Bedrock + metadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + // Used with the OptionalPack + metadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + protected boolean isShaking(GeyserSession session) { + return isTransforming || super.isShaking(session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java index ad9f059ab37..7e92a75699e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java @@ -25,13 +25,45 @@ package org.geysermc.connector.entity.living.monster.raid; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; public class SpellcasterIllagerEntity extends AbstractIllagerEntity { + private static final int SUMMON_VEX_PARTICLE_COLOR = (179 << 16) | (179 << 8) | 204; + private static final int ATTACK_PARTICLE_COLOR = (102 << 16) | (77 << 8) | 89; + private static final int WOLOLO_PARTICLE_COLOR = (179 << 16) | (128 << 8) | 51; public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // OptionalPack usage + metadata.getFlags().setFlag(EntityFlag.BRIBED, this.entityType == EntityType.ILLUSIONER); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + int spellType = (int) (byte) entityMetadata.getValue(); + // Summon vex, attack, or wololo + metadata.getFlags().setFlag(EntityFlag.CASTING, spellType == 1 || spellType == 2 || spellType == 3); + int rgbData = 0; + // Set the spell color based on Java values + switch (spellType) { + case 1: + rgbData = SUMMON_VEX_PARTICLE_COLOR; + break; + case 2: + rgbData = ATTACK_PARTICLE_COLOR; + break; + case 3: + rgbData = WOLOLO_PARTICLE_COLOR; + break; + } + metadata.put(EntityData.EVOKER_SPELL_COLOR, rgbData); + } + super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java new file mode 100644 index 00000000000..4a936039387 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/VindicatorEntity.java @@ -0,0 +1,49 @@ +/* + * 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.connector.entity.living.monster.raid; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class VindicatorEntity extends AbstractIllagerEntity { + + public VindicatorEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Allow the axe to be shown if necessary + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 4) == 4); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index b8be69ab3ad..3cda303cddb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -27,6 +27,7 @@ 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.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -82,6 +83,9 @@ public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vecto profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + + // For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior + metadata.put(EntityData.MARK_VARIANT, 0xff); } @Override @@ -270,7 +274,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Extra hearts - is not metadata but an attribute on Bedrock - if (entityMetadata.getId() == 14) { + if (entityMetadata.getId() == 15) { UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(geyserId); List attributes = new ArrayList<>(); @@ -280,11 +284,19 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s session.sendUpstreamPacket(attributesPacket); } + if (entityMetadata.getId() == 17) { + // OptionalPack usage for toggling skin bits + // In Java Edition, a bit being set means that part should be enabled + // However, to ensure that the pack still works on other servers, we invert the bit so all values by default + // are true (0). + metadata.put(EntityData.MARK_VARIANT, ~((byte) entityMetadata.getValue()) & 0xff); + } + // Parrot occupying shoulder - if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 19 || entityMetadata.getId() == 20) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); if (tag != null && !tag.isEmpty()) { - if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) { + if ((entityMetadata.getId() == 19 && leftParrot != null) || (entityMetadata.getId() == 20 && rightParrot != null)) { // No need to update a parrot's data when it already exists return; } @@ -310,10 +322,10 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s rightParrot = parrot; } } else { - Entity parrot = (entityMetadata.getId() == 18 ? leftParrot : rightParrot); + Entity parrot = (entityMetadata.getId() == 19 ? leftParrot : rightParrot); if (parrot != null) { parrot.despawnEntity(session); - if (entityMetadata.getId() == 18) { + if (entityMetadata.getId() == 19) { leftParrot = null; } else { rightParrot = null; @@ -323,6 +335,26 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } } + @Override + protected void setDimensions(Pose pose) { + float height; + switch (pose) { + case SNEAKING: + height = 1.5f; + break; + case FALL_FLYING: + case SPIN_ATTACK: + case SWIMMING: + height = 0.6f; + break; + default: + super.setDimensions(pose); + return; + } + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + } + @Override public void updateBedrockAttributes(GeyserSession session) { // TODO: Don't use duplicated code if (!valid) return; diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f38e56fd88d..582e2cbfac8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -36,10 +36,7 @@ import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; import org.geysermc.connector.entity.living.merchant.VillagerEntity; import org.geysermc.connector.entity.living.monster.*; -import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; -import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; -import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; -import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; +import org.geysermc.connector.entity.living.monster.raid.*; import org.geysermc.connector.entity.player.PlayerEntity; import java.util.ArrayList; @@ -48,7 +45,7 @@ @Getter public enum EntityType { - CHICKEN(AnimalEntity.class, 10, 0.7f, 0.4f), + CHICKEN(ChickenEntity.class, 10, 0.7f, 0.4f), COW(AnimalEntity.class, 11, 1.4f, 0.9f), PIG(PigEntity.class, 12, 0.9f), SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), @@ -58,7 +55,7 @@ public enum EntityType { SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), BAT(BatEntity.class, 19, 0.9f, 0.5f), - IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), + IRON_GOLEM(IronGolemEntity.class, 20, 2.7f, 1.4f), SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), @@ -74,7 +71,7 @@ public enum EntityType { ZOMBIE(ZombieEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f), GIANT(GiantEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie"), CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), - SKELETON(AbstractSkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), + SKELETON(SkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), ZOMBIFIED_PIGLIN(ZombifiedPiglinEntity.class, 36, 1.95f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), SLIME(SlimeEntity.class, 37, 0.51f), @@ -97,10 +94,10 @@ public enum EntityType { SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), - VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), + VINDICATOR(VindicatorEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f), - PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), + PHANTOM(PhantomEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f), ARMOR_STAND(ArmorStandEntity.class, 61, 1.975f, 0.5f), @@ -133,7 +130,7 @@ public enum EntityType { THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f), - BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), + BOAT(BoatEntity.class, 90, 0.6f, 1.6f, 1.6f, 0.35f), WITHER_SKULL_DANGEROUS(WitherSkullEntity.class, 91, 0f), LIGHTNING_BOLT(Entity.class, 93, 0f), SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), @@ -150,7 +147,7 @@ public enum EntityType { EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), - BALLOON(Entity.class, 107, 0f), //TODO + BALLOON(Entity.class, 107, 0f), PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), @@ -164,16 +161,21 @@ public enum EntityType { ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), + AXOLOTL(AxolotlEntity.class, 0, 0.42f, 0.7f, 0.7f, 0f, "minecraft:axolotl"), + GLOW_SQUID(GlowSquidEntity.class, 0, 0.8f, 0.8f, 0.8f, 0f, "minecraft:glow_squid"), + GOAT(GoatEntity.class, 0, 1.3f, 0.9f, 0.9f, 0f, "minecraft:goat"), + MARKER(Entity.class, 0, 0, 0, 0, 0, "minecraft:marker"), // Only should be used for ALL_JAVA_IDENTIFIERS /** * Item frames are handled differently since they are a block in Bedrock. */ ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), + GLOW_ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), /** - * Not an entity in Bedrock, so we replace it with a Pillager + * Not an entity in Bedrock, so we replace it with an evoker */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + ILLUSIONER(SpellcasterIllagerEntity.class, 104, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:evocation_illager"), /** * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes @@ -188,7 +190,7 @@ public enum EntityType { static { List allJavaIdentifiers = new ArrayList<>(); - for (EntityType type : values()) { + for (EntityType type : VALUES) { if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) { continue; } diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ClientConfirmTransactionPacketSend.java b/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ClientConfirmTransactionPacketSend.java deleted file mode 100644 index 37f60877894..00000000000 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ClientConfirmTransactionPacketSend.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.event.events.packet.downstream; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketSendEvent; -import org.geysermc.connector.network.session.GeyserSession; - -public class ClientConfirmTransactionPacketSend extends DownstreamPacketSendEvent { - public ClientConfirmTransactionPacketSend(@NonNull GeyserSession session, @NonNull ClientConfirmTransactionPacket packet) { - super(session, packet); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerConfirmTransactionPacketReceive.java b/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerConfirmTransactionPacketReceive.java deleted file mode 100644 index bfe83927a84..00000000000 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerConfirmTransactionPacketReceive.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.event.events.packet.downstream; - -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; - -public class ServerConfirmTransactionPacketReceive extends DownstreamPacketReceiveEvent { - public ServerConfirmTransactionPacketReceive(@NonNull GeyserSession session, @NonNull ServerConfirmTransactionPacket packet) { - super(session, packet); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityMovementPacketReceive.java b/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityMovementPacketReceive.java deleted file mode 100644 index 9c9427d85e4..00000000000 --- a/connector/src/main/java/org/geysermc/connector/event/events/packet/downstream/ServerEntityMovementPacketReceive.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.event.events.packet.downstream; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityMovementPacket; -import lombok.NonNull; -import org.geysermc.connector.event.events.packet.DownstreamPacketReceiveEvent; -import org.geysermc.connector.network.session.GeyserSession; - -public class ServerEntityMovementPacketReceive extends DownstreamPacketReceiveEvent { - public ServerEntityMovementPacketReceive(@NonNull GeyserSession session, @NonNull ServerEntityMovementPacket packet) { - super(session, packet); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 11a0034ad13..c2a8ddca142 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -53,7 +53,7 @@ public class Inventory { @Setter protected String title; - protected GeyserItemStack[] items; + protected final GeyserItemStack[] items; /** * The location of the inventory block. Will either be a fake block above the player's head, or the actual block location @@ -66,9 +66,6 @@ public class Inventory { @Setter protected long holderId = -1; - @Getter - protected short transactionId = 0; - @Getter @Setter private boolean pending = false; @@ -114,10 +111,6 @@ protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack n } } - public short getNextTransactionId() { - return ++transactionId; - } - @Override public String toString() { return "Inventory{" + @@ -127,7 +120,6 @@ public String toString() { ", items=" + Arrays.toString(items) + ", holderPosition=" + holderPosition + ", holderId=" + holderId + - ", transactionId=" + transactionId + '}'; } } diff --git a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java index 1457780b1e6..6ebca3f8c9f 100644 --- a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java +++ b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java @@ -152,20 +152,15 @@ private ObjectNode getPluginData() { */ private ObjectNode getServerData() { // OS specific data - int playerAmount = connector.getPlayers().size(); - String osName = System.getProperty("os.name"); String osArch = System.getProperty("os.arch"); String osVersion = System.getProperty("os.version"); - String javaVersion = System.getProperty("java.version"); int coreCount = Runtime.getRuntime().availableProcessors(); ObjectNode data = mapper.createObjectNode(); data.put("serverUUID", serverUUID); - data.put("playerAmount", playerAmount); - data.put("javaVersion", javaVersion); data.put("osName", osName); data.put("osArch", osArch); data.put("osVersion", osVersion); diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index b7365c05d0c..84fc449e97b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,10 +26,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; -import com.nukkitx.protocol.bedrock.v422.Bedrock_v422; -import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; -import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; +import com.nukkitx.protocol.bedrock.v440.Bedrock_v440; import java.util.ArrayList; import java.util.List; @@ -42,20 +39,13 @@ public class BedrockProtocol { * 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_v431.V431_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v440.V440_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder() - .minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta - .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v422.V422_CODEC.toBuilder() - .minecraftVersion("1.16.200/1.16.201") - .build()); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v428.V428_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } diff --git a/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java index fd8d5e4f8e8..c019cdb8935 100644 --- a/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java @@ -38,9 +38,8 @@ * packets of interest and limit boilerplate code. */ public class LoggingPacketHandler implements BedrockPacketHandler { - - protected GeyserConnector connector; - protected GeyserSession session; + protected final GeyserConnector connector; + protected final GeyserSession session; LoggingPacketHandler(GeyserConnector connector, GeyserSession session) { this.connector = connector; @@ -759,9 +758,6 @@ public boolean handle(DebugInfoPacket packet) { return defaultHandler(packet); } - // I question if God exists because of this packet - God does not exist if I find out there's a built-in dab - // TODO for the future: redirect this as a /me command - // TODO for the far future: should we have a client mod that handles skins, handle these too @Override public boolean handle(EmoteListPacket packet) { return defaultHandler(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 87541f70409..2d8f7d17c68 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -83,7 +83,7 @@ public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, B * @return if the packet is a query packet */ private boolean isQueryPacket(ByteBuf buffer) { - return (buffer.readableBytes() >= 2) ? buffer.readUnsignedShort() == 0xFEFD : false; + return buffer.readableBytes() >= 2 && buffer.readUnsignedShort() == 0xFEFD; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 0b48c9cb384..180eda1c119 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -30,7 +30,6 @@ 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.v428.Bedrock_v428; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; @@ -43,11 +42,9 @@ import org.geysermc.connector.event.events.packet.upstream.ResourcePackClientResponsePacketReceive; import org.geysermc.connector.event.events.packet.upstream.SetLocalPlayerAsInitializedPacketReceive; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; import org.geysermc.connector.utils.*; import java.io.FileInputStream; @@ -96,8 +93,7 @@ public boolean handle(LoginPacket loginPacket) { session.getUpstream().getSession().setPacketCodec(packetCodec); // Set the block translation based off of version - session.setBlockTranslator(packetCodec.getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion() - ? BlockTranslator1_16_210.INSTANCE : BlockTranslator1_16_100.INSTANCE); + session.setBlockTranslator(BlockTranslator1_17_0.INSTANCE); LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); @@ -187,24 +183,8 @@ public boolean handle(ModalFormResponsePacket packet) { return true; } - packet = result.getEvent().getPacket(); - - switch (packet.getFormId()) { - case AdvancementsCache.ADVANCEMENT_INFO_FORM_ID: - return session.getAdvancementsCache().handleInfoForm(packet.getFormData()); - case AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID: - return session.getAdvancementsCache().handleListForm(packet.getFormData()); - case AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID: - return session.getAdvancementsCache().handleMenuForm(packet.getFormData()); - case SettingsUtils.SETTINGS_FORM_ID: - return SettingsUtils.handleSettingsForm(session, packet.getFormData()); - case StatisticsUtils.STATISTICS_LIST_FORM_ID: - return StatisticsUtils.handleListForm(session, packet.getFormData()); - case StatisticsUtils.STATISTICS_MENU_FORM_ID: - return StatisticsUtils.handleMenuForm(session, packet.getFormData()); - } - - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + session.getFormCache().handleResponse(packet); + return true; } private boolean couldLoginUserByName(String bedrockUsername) { @@ -239,7 +219,7 @@ public boolean handle(SetLocalPlayerAsInitializedPacket packet) { if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { - LoginEncryptionUtils.showLoginWindow(session); + LoginEncryptionUtils.buildAndShowLoginWindow(session); } // else we were able to log the user in } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index d146ab48492..c76515f09dc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -47,10 +47,9 @@ import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket; import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.packetlib.BuiltinFlags; -import com.github.steveice10.packetlib.Client; import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; -import com.github.steveice10.packetlib.tcp.TcpSessionFactory; +import com.github.steveice10.packetlib.tcp.TcpClientSession; import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.*; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -60,27 +59,25 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; +import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; @@ -107,18 +104,17 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.util.FormBuilder; +import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.EncryptionUtil; -import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -129,7 +125,7 @@ public class GeyserSession implements CommandSender { private final GeyserConnector connector; private final UpstreamSession upstream; - private Client downstream; + private TcpClientSession downstream; @Setter private AuthData authData; @Setter @@ -155,8 +151,10 @@ public class GeyserSession implements CommandSender { private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; + private final FormCache formCache; + private final PreferencesCache preferencesCache; + private final TagCache tagCache; private WorldCache worldCache; - private WindowCache windowCache; private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); private final PlayerInventory playerInventory; @@ -197,10 +195,10 @@ public class GeyserSession implements CommandSender { private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); /** - * A map of Vector3i positions to Java entity IDs. + * A map of Vector3i positions to Java entities. * Used for translating Bedrock block actions to Java entity actions. */ - private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); + private final Map itemFrameCache = new Object2ObjectOpenHashMap<>(); /** * Stores a list of all lectern locations and their block entity tags. @@ -243,12 +241,6 @@ public class GeyserSession implements CommandSender { @Setter private boolean sprinting; - /** - * Not updated if cache chunks is enabled. - */ - @Setter - private boolean jumping; - /** * Whether the player is swimming in water. * Used to update speed when crawling. @@ -378,9 +370,6 @@ public class GeyserSession implements CommandSender { private boolean reducedDebugInfo = false; - @Setter - private CustomFormWindow settingsForm; - /** * The op permission level set by the server */ @@ -424,7 +413,7 @@ public class GeyserSession implements CommandSender { /** * Stores the last text inputted into a sign. - * + *

* Bedrock sends packets every time you update the sign, Java only wants the final packet. * Until we determine that the user has finished editing, we save the sign's current status. */ @@ -443,9 +432,7 @@ public class GeyserSession implements CommandSender { @Setter private boolean waitingForStatistics = false; - @Setter - private List selectedEmotes = new ArrayList<>(); - private final Set emotes = new HashSet<>(); + private final Set emotes; /** * The thread that will run every 50 milliseconds - one Minecraft tick. @@ -463,8 +450,10 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); + this.formCache = new FormCache(this); + this.preferencesCache = new PreferencesCache(this); + this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); - this.windowCache = new WindowCache(this); this.collisionManager = new CollisionManager(this); @@ -485,8 +474,14 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ .onCancelled(result -> disconnect(result.getEvent().getMessage())); // Make a copy to prevent ConcurrentModificationException - final List tmpPlayers = new ArrayList<>(connector.getPlayers()); - tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); + if (connector.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) { + this.emotes = new HashSet<>(); + // Make a copy to prevent ConcurrentModificationException + final List tmpPlayers = new ArrayList<>(connector.getPlayers()); + tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); + } else { + this.emotes = null; + } bedrockServerSession.addDisconnectHandler(disconnectReason -> { EventManager.getInstance().triggerEvent(new SessionDisconnectEvent(this, disconnectReason)); @@ -527,12 +522,7 @@ public void connect() { sendUpstreamPacket(entityPacket); CreativeContentPacket creativePacket = new CreativeContentPacket(); - if (upstream.getSession().getPacketCodec().getProtocolVersion() < Bedrock_v431.V431_CODEC.getProtocolVersion()) { - creativePacket.setContents(ItemRegistry.getPre1_16_220CreativeContents()); - } else { - // No additional work required - creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); - } + creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); sendUpstreamPacket(creativePacket); PlayStatusPacket playStatusPacket = new PlayStatusPacket(); @@ -599,9 +589,18 @@ public void authenticate(String username, String password) { authenticationService.setPassword(password); authenticationService.login(); - protocol = new MinecraftProtocol(authenticationService); + protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { - protocol = new MinecraftProtocol(username); + // always replace spaces when using Floodgate, + // as usernames with spaces cause issues with Bungeecord's login cycle. + // However, this doesn't affect the final username as Floodgate is still in charge of that. + // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear. + String validUsername = username; + if (remoteAuthType == AuthType.FLOODGATE) { + validUsername = username.replace(' ', '_'); + } + + protocol = new MinecraftProtocol(validUsername); } connectDownstream(); @@ -630,7 +629,7 @@ public void authenticateWithMicrosoftCode() { MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode(); - LoginEncryptionUtils.showMicrosoftCodeWindow(this, response); + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); // This just looks cool SetTimePacket packet = new SetTimePacket(); @@ -657,7 +656,7 @@ private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticatio } try { msaAuthenticationService.login(); - protocol = new MinecraftProtocol(msaAuthenticationService); + protocol = new MinecraftProtocol(msaAuthenticationService.getSelectedProfile(), msaAuthenticationService.getAccessToken()); connectDownstream(); } catch (RequestException e) { @@ -675,61 +674,61 @@ private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticatio */ private void connectDownstream() { boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE; - final PublicKey publicKey; - - if (floodgate) { - PublicKey key = null; - try { - key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyPath(), - PublicKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); - } - publicKey = key; - } else publicKey = null; - - if (publicKey != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); - } // Start ticking tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); - downstream = new Client(this.remoteAddress, this.remotePort, protocol, new TcpSessionFactory()); + downstream = new TcpClientSession(this.remoteAddress, this.remotePort, protocol); disableSrvResolving(); if (connector.getConfig().getRemote().isUseProxyProtocol()) { - downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); - downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); + downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); + downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } if (connector.getConfig().isForwardPlayerPing()) { // Let Geyser handle sending the keep alive - downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); + downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); } - downstream.getSession().addListener(new SessionAdapter() { + downstream.addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { //todo move this somewhere else if (event.getPacket() instanceof HandshakePacket) { String addressSuffix; if (floodgate) { - String encrypted = ""; + byte[] encryptedData; + try { - encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( + FloodgateSkinUploader skinUploader = connector.getSkinUploader(); + FloodgateCipher cipher = connector.getCipher(); + + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), authData.getXboxUUID(), - clientData.getDeviceOS().ordinal(), + clientData.getDeviceOs().ordinal(), clientData.getLanguageCode(), + clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getAddress().getAddress().getHostAddress() - )); + upstream.getAddress().getAddress().getHostAddress(), + skinUploader.getId(), + skinUploader.getVerifyCode(), + connector.getTimeSyncer() + ).toString()); + + if (!connector.getTimeSyncer().hasUsefulOffset()) { + connector.getLogger().warning( + "We couldn't make sure that your system clock is accurate. " + + "This can cause issues with logging in." + ); + } + } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); + return; } - addressSuffix = '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted; + addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8); } else { addressSuffix = ""; } @@ -811,6 +810,12 @@ public void packetReceived(PacketReceivedEvent event) { if (!closed) { handleDownstreamPacket(event.getPacket()); } + + if (remoteAuthType == AuthType.FLOODGATE) { + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); + } } @Override @@ -825,7 +830,7 @@ public void packetError(PacketErrorEvent event) { if (!daylightCycle) { setDaylightCycle(true); } - downstream.getSession().connect(); + downstream.connect(); connector.addPlayer(this); } @@ -843,7 +848,7 @@ public void handleDownstreamPacket(Packet packet) { } EventResult> result = EventManager.getInstance().triggerEvent(DownstreamPacketReceiveEvent.of(this, packet)); - if (!result.isCancelled()) { + if (result.getEvent() != null && !result.isCancelled()) { PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(result.getEvent().getPacket().getClass(), result.getEvent().getPacket(), this); } } @@ -851,8 +856,8 @@ public void handleDownstreamPacket(Packet packet) { public void disconnect(String reason) { if (!closed) { loggedIn = false; - if (downstream != null && downstream.getSession() != null) { - downstream.getSession().disconnect(reason); + if (downstream != null) { + downstream.disconnect(reason); } if (upstream != null && !upstream.isClosed()) { connector.getPlayers().remove(this); @@ -870,7 +875,6 @@ public void disconnect(String reason) { this.entityCache = null; this.effectCache = null; this.worldCache = null; - this.windowCache = null; closed = true; } @@ -882,7 +886,7 @@ public void close() { /** * Called every 50 milliseconds - one Minecraft tick. */ - public void tick() { + protected void tick() { // Check to see if the player's position needs updating - a position update should be sent once every 3 seconds if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) { // Recalculate in case something else changed position @@ -980,7 +984,7 @@ public boolean adjustSpeed() { * Will be overwritten for GeyserConnect. */ protected void disableSrvResolving() { - this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); + this.downstream.setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); } @Override @@ -1011,10 +1015,6 @@ public String getLocale() { return clientData.getLanguageCode(); } - public void sendForm(FormWindow window, int id) { - windowCache.showWindow(window, id); - } - public void setRenderDistance(int renderDistance) { renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle this.renderDistance = renderDistance; @@ -1028,8 +1028,12 @@ public InetSocketAddress getSocketAddress() { return this.upstream.getAddress(); } - public void sendForm(FormWindow window) { - windowCache.showWindow(window); + public void sendForm(Form form) { + formCache.showForm(form); + } + + public void sendForm(FormBuilder formBuilder) { + formCache.showForm(formBuilder.build()); } private void startGame() { @@ -1081,7 +1085,7 @@ private void startGame() { startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setInventoriesServerAuthoritative(true); - startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped + startGamePacket.setServerEngine(""); // Do we want to fill this in? SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); settings.setMovementMode(AuthoritativeMovementMode.CLIENT); @@ -1263,8 +1267,8 @@ public void sendUpstreamPacketImmediately(BedrockPacket packet) { public void sendDownstreamPacket(Packet packet) { EventManager.getInstance().triggerEvent(DownstreamPacketSendEvent.of(this, packet)) .onNotCancelled(result -> { - if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { - downstream.getSession().send(result.getEvent().getPacket()); + if (downstream != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { + downstream.send(result.getEvent().getPacket()); } else { connector.getLogger().debug("Tried to send downstream packet " + result.getEvent().getPacket().getClass().getSimpleName() + " before connected to the server"); } @@ -1280,7 +1284,7 @@ public void sendDownstreamPacket(Packet packet) { public void setReducedDebugInfo(boolean value) { reducedDebugInfo = value; // Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable. - getWorldCache().updateShowCoordinates(); + preferencesCache.updateShowCoordinates(); } /** @@ -1299,7 +1303,7 @@ public void setDaylightCycle(boolean doCycle) { * Send a gamerule value to the client * * @param gameRule The gamerule to send - * @param value The value of the gamerule + * @param value The value of the gamerule */ public void sendGameRule(String gameRule, Object value) { GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); @@ -1366,7 +1370,6 @@ public void updateStatistics(@NonNull Map statistics) { } public void refreshEmotes(List emotes) { - this.selectedEmotes = emotes; this.emotes.addAll(emotes); for (GeyserSession player : connector.getPlayers()) { List pieces = new ArrayList<>(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index ba9e13548fa..46327689177 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -25,16 +25,26 @@ package org.geysermc.connector.network.session.auth; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.connector.GeyserConnector; import java.util.UUID; -@Getter -@AllArgsConstructor +@RequiredArgsConstructor public class AuthData { + @Getter private final String name; + @Getter private final UUID UUID; + @Getter private final String xboxUUID; - private String name; - private UUID UUID; - private String xboxUUID; + private final JsonNode certChainData; + private final String clientData; + + public void upload(GeyserConnector connector) { + // 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 + connector.getSkinUploader().uploadSkin(certChainData, clientData); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 16e06c06644..fc4d1164a63 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -25,17 +25,18 @@ package org.geysermc.connector.network.session.auth; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.UiProfile; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class BedrockClientData { +public final class BedrockClientData { @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -77,9 +78,9 @@ public class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOS deviceOS; + private DeviceOs deviceOs; @JsonProperty(value = "UIProfile") - private UIProfile uiProfile; + private UiProfile uiProfile; @JsonProperty(value = "GuiScale") private int guiScale; @JsonProperty(value = "CurrentInputMode") @@ -106,18 +107,19 @@ public class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; - public enum UIProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET + public DeviceOs getDeviceOs() { + return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } - public enum InputMode { - @JsonEnumDefaultValue - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, // I guess Touch? - CONTROLLER, - VR + public InputMode getCurrentInputMode() { + return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN; + } + + public InputMode getDefaultInputMode() { + return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN; + } + + public UiProfile getUiProfile() { + return uiProfile != null ? uiProfile : UiProfile.CLASSIC; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index 369967accdb..98ec5b26258 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,26 +29,19 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.HashMap; import java.util.List; import java.util.Map; public class AdvancementsCache { - - // Different form IDs - public static final int ADVANCEMENTS_MENU_FORM_ID = 1341; - public static final int ADVANCEMENTS_LIST_FORM_ID = 1342; - public static final int ADVANCEMENT_INFO_FORM_ID = 1343; - /** * Stores the player's advancement progress */ @@ -74,73 +67,128 @@ public AdvancementsCache(GeyserSession session) { } /** - * Build a form with all advancement categories - * - * @return The built advancement category menu + * Build and send a form with all advancement categories */ - public SimpleFormWindow buildMenuForm() { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + public void buildAndShowMenuForm() { + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(LocaleUtils::getLocaleString, session.getLocale()) + .title("gui.advancements"); - // Created menu window for advancement categories - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), ""); + boolean hasAdvancements = false; for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language))); + hasAdvancements = true; + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); } } - if (window.getButtons().isEmpty()) { - window.setContent(LocaleUtils.getLocaleString("advancements.empty", language)); + if (!hasAdvancements) { + builder.content("advancements.empty"); } - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + String id = ""; + + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == response.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + buildAndShowListForm(); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + }); + + session.sendForm(builder); } /** - * Builds the list of advancements - * - * @return The built list form + * Build and send the list of advancements */ - public SimpleFormWindow buildListForm() { - // Cache the language for easier access - String language = session.getLocale(); - String id = currentAdvancementCategoryId; + public void buildAndShowListForm() { GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + String language = session.getLocale(); - // Create the window - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language), - MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + SimpleForm.Builder builder = + SimpleForm.builder() + .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language)) + .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); - if (id != null) { - for (Map.Entry advancementEntry : storedAdvancements.entrySet()) { - GeyserAdvancement advancement = advancementEntry.getValue(); + if (currentAdvancementCategoryId != null) { + for (GeyserAdvancement advancement : storedAdvancements.values()) { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { - boolean earned = isEarned(advancement); - - if (earned || !advancement.getDisplayData().isShowToast()) { - window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } else { - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } + boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); + builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } } - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language)); - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + return; + } + + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == response.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + + if (advancement != null) { + buildAndShowInfoForm(advancement); + } else { + buildAndShowMenuForm(); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + }); + + session.sendForm(builder); } /** * Builds the advancement display info based on the chosen category * * @param advancement The advancement used to create the info display - * @return The information for the chosen advancement */ - public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { + public void buildAndShowInfoForm(GeyserAdvancement advancement) { // Cache language for easier access String language = session.getLocale(); @@ -160,16 +208,24 @@ public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { Parent Advancement: Minecraft // If relevant */ - String content = description + "\n\n§f" + - earnedString + "\n"; + String content = description + "\n\n§f" + earnedString + "\n"; if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { // Only display the parent if it is not the category content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); } - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); - return window; + session.sendForm( + SimpleForm.builder() + .title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle())) + .content(content) + .button(LanguageUtils.getPlayerLocaleString("gui.back", language)) + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + buildAndShowListForm(); + } + }) + ); } /** @@ -209,108 +265,6 @@ public boolean isEarned(GeyserAdvancement advancement) { return earned; } - /** - * Handle the menu form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleMenuForm(String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID); - menuForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - - String id = ""; - if (formResponse != null && formResponse.getClickedButton() != null) { - int advancementIndex = 0; - for (Map.Entry advancement : storedAdvancements.entrySet()) { - if (advancement.getValue().getParentId() == null) { // Root advancement - if (advancementIndex == formResponse.getClickedButtonId()) { - id = advancement.getKey(); - break; - } else { - advancementIndex++; - } - } - } - } - if (!id.equals("")) { - if (id.equals(currentAdvancementCategoryId)) { - // The server thinks we are already on this tab - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } else { - // Send a packet indicating that we intend to open this particular advancement window - ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); - session.sendDownstreamPacket(packet); - // Wait for a response there - } - } - - return true; - } - - /** - * Handle the list form response (Advancement category choice) - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleListForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - GeyserAdvancement advancement = null; - int advancementIndex = 0; - // Loop around to find the advancement that the client pressed - for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { - if (advancementEntry.getParentId() != null && - currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { - if (advancementIndex == formResponse.getClickedButtonId()) { - advancement = advancementEntry; - break; - } else { - advancementIndex++; - } - } - } - if (advancement != null) { - session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID); - } else { - session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID); - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - } else { - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - - return true; - } - - /** - * Handle the info form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleInfoForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } - - return true; - } - public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java index c82645dbfdb..cb373789577 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java @@ -68,7 +68,7 @@ public void checkForSend() { packet = null; return; } - session.getDownstream().getSession().send(packet); + session.sendDownstreamPacket(packet); packet = null; lastBookUpdate = System.currentTimeMillis(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index c1ba6fff16d..28ac9459ef4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -37,15 +37,14 @@ @AllArgsConstructor public class BossBar { + private final GeyserSession session; - private GeyserSession session; - - private long entityId; + private final long entityId; private Component title; private float health; - private int color; - private int overlay; - private int darkenSky; + private final int color; + private final int overlay; + private final int darkenSky; public void addBossBar() { addBossEntity(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index d182a6f12b2..393a1f1880f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -29,47 +29,29 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Column; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.MathUtils; public class ChunkCache { - private static final int MINIMUM_WORLD_HEIGHT = 0; - private final boolean cache; - private final Long2ObjectMap chunks; + @Setter + private int minY; + public ChunkCache(GeyserSession session) { - if (session.getConnector().getWorldManager().hasOwnChunkCache()) { - this.cache = false; // To prevent Spigot from initializing - } else { - this.cache = session.getConnector().getConfig().isCacheChunks(); - } + this.cache = !session.getConnector().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing chunks = cache ? new Long2ObjectOpenHashMap<>() : null; } - public Column addToCache(Column chunk) { + public void addToCache(Column chunk) { if (!cache) { - return chunk; + return; } - long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()); - Column existingChunk; - if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk - && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing - boolean changed = false; - for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away - if (chunk.getChunks()[i] != null) { - existingChunk.getChunks()[i] = chunk.getChunks()[i]; - changed = true; - } - } - return changed ? existingChunk : null; - } else { - chunks.put(chunkPosition, chunk); - return chunk; - } + chunks.put(MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()), chunk); } public Column getChunk(int chunkX, int chunkZ) { @@ -87,15 +69,24 @@ public void updateBlock(int x, int y, int z, int block) { return; } - if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + if (y < minY || (y >> 4) > column.getChunks().length - 1) { // Y likely goes above or below the height limit of this world return; } - Chunk chunk = column.getChunks()[y >> 4]; - if (chunk != null) { - chunk.set(x & 0xF, y & 0xF, z & 0xF, block); + Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()]; + if (chunk == null) { + if (block != BlockTranslator.JAVA_AIR_ID) { + chunk = new Chunk(); + // A previously empty chunk, which is no longer empty as a block has been added to it + column.getChunks()[(y >> 4) - getChunkMinY()] = chunk; + } else { + // Nothing to update + return; + } } + + chunk.set(x & 0xF, y & 0xF, z & 0xF, block); } public int getBlockAt(int x, int y, int z) { @@ -108,12 +99,12 @@ public int getBlockAt(int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } - if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + if (y < minY || (y >> 4) > column.getChunks().length - 1) { // Y likely goes above or below the height limit of this world return BlockTranslator.JAVA_AIR_ID; } - Chunk chunk = column.getChunks()[y >> 4]; + Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()]; if (chunk != null) { return chunk.get(x & 0xF, y & 0xF, z & 0xF); } @@ -129,4 +120,8 @@ public void removeChunk(int chunkX, int chunkZ) { long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); chunks.remove(chunkPosition); } + + public int getChunkMinY() { + return minY >> 4; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java new file mode 100644 index 00000000000..954a2b033fe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.session.cache; + +import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.RequiredArgsConstructor; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.SimpleForm; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@RequiredArgsConstructor +public class FormCache { + private final AtomicInteger formId = new AtomicInteger(0); + private final Int2ObjectMap

forms = new Int2ObjectOpenHashMap<>(); + private final GeyserSession session; + + public int addForm(Form form) { + int windowId = formId.getAndIncrement(); + forms.put(windowId, form); + return windowId; + } + + public int showForm(Form form) { + int windowId = addForm(form); + + ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); + formRequestPacket.setFormId(windowId); + formRequestPacket.setFormData(form.getJsonData()); + session.sendUpstreamPacket(formRequestPacket); + + // Hack to fix the (url) image loading bug + if (form instanceof SimpleForm) { + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + } + + return windowId; + } + + public void handleResponse(ModalFormResponsePacket response) { + Form form = forms.remove(response.getFormId()); + if (form == null) { + return; + } + + Consumer responseConsumer = form.getResponseHandler(); + if (responseConsumer != null) { + try { + responseConsumer.accept(response.getFormData()); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error("Error while processing form response!", e); + } + } + } + + public boolean removeWindow(int id) { + return forms.remove(id) != null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java new file mode 100644 index 00000000000..d477066c219 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java @@ -0,0 +1,71 @@ +/* + * 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.connector.network.session.cache; + +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.CooldownUtils; + +@Getter +public class PreferencesCache { + private final GeyserSession session; + + /** + * True if the client prefers being shown their coordinates, regardless if they're being shown or not. + * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. + */ + @Setter + private boolean prefersShowCoordinates = true; + /** + * If the client's preference will be ignored, this will return false. + */ + private boolean allowShowCoordinates; + + /** + * Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}. + */ + @Setter + private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); + + public PreferencesCache(GeyserSession session) { + this.session = session; + } + + /** + * Tell the client to hide or show the coordinates. + * + * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
+ *
+ * {@link GeyserSession#reducedDebugInfo} is enabled + * {@link GeyserConfiguration#isShowCoordinates()} is disabled + */ + public void updateShowCoordinates() { + allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); + session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java new file mode 100644 index 00000000000..b1427a86493 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java @@ -0,0 +1,146 @@ +/* + * 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.connector.network.session.cache; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareTagsPacket; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.registry.type.BlockMapping; + +import java.util.Map; + +/** + * Manages information sent from the {@link ServerDeclareTagsPacket}. If that packet is not sent, all lists here + * will remain empty, matching Java Edition behavior. + */ +public class TagCache { + /* Blocks */ + private IntList leaves; + private IntList wool; + + private IntList axeEffective; + private IntList hoeEffective; + private IntList pickaxeEffective; + private IntList shovelEffective; + + private IntList requiresStoneTool; + private IntList requiresIronTool; + private IntList requiresDiamondTool; + + /* Items */ + private IntList flowers; + private IntList foxFood; + private IntList piglinLoved; + + public TagCache() { + // Ensure all lists are non-null + clear(); + } + + public void loadPacket(ServerDeclareTagsPacket packet) { + Map blockTags = packet.getTags().get("minecraft:block"); + this.leaves = IntList.of(blockTags.get("minecraft:leaves")); + this.wool = IntList.of(blockTags.get("minecraft:wool")); + + this.axeEffective = IntList.of(blockTags.get("minecraft:mineable/axe")); + this.hoeEffective = IntList.of(blockTags.get("minecraft:mineable/hoe")); + this.pickaxeEffective = IntList.of(blockTags.get("minecraft:mineable/pickaxe")); + this.shovelEffective = IntList.of(blockTags.get("minecraft:mineable/shovel")); + + this.requiresStoneTool = IntList.of(blockTags.get("minecraft:needs_stone_tool")); + this.requiresIronTool = IntList.of(blockTags.get("minecraft:needs_iron_tool")); + this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); + + Map itemTags = packet.getTags().get("minecraft:item"); + 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")); + } + + public void clear() { + this.leaves = IntLists.emptyList(); + this.wool = IntLists.emptyList(); + + this.axeEffective = IntLists.emptyList(); + this.hoeEffective = IntLists.emptyList(); + this.pickaxeEffective = IntLists.emptyList(); + this.shovelEffective = IntLists.emptyList(); + + this.requiresStoneTool = IntLists.emptyList(); + this.requiresIronTool = IntLists.emptyList(); + this.requiresDiamondTool = IntLists.emptyList(); + + this.flowers = IntLists.emptyList(); + this.foxFood = IntLists.emptyList(); + this.piglinLoved = IntLists.emptyList(); + } + + public boolean isFlower(ItemEntry itemEntry) { + return flowers.contains(itemEntry.getJavaId()); + } + + public boolean isFoxFood(ItemEntry itemEntry) { + return foxFood.contains(itemEntry.getJavaId()); + } + + public boolean shouldPiglinAdmire(ItemEntry itemEntry) { + return piglinLoved.contains(itemEntry.getJavaId()); + } + + public boolean isAxeEffective(BlockMapping blockMapping) { + return axeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isHoeEffective(BlockMapping blockMapping) { + return hoeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isPickaxeEffective(BlockMapping blockMapping) { + return pickaxeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShovelEffective(BlockMapping blockMapping) { + return shovelEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShearsEffective(BlockMapping blockMapping) { + int javaBlockId = blockMapping.getJavaBlockId(); + return leaves.contains(javaBlockId) || wool.contains(javaBlockId); + } + + public boolean requiresStoneTool(BlockMapping blockMapping) { + return requiresStoneTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresIronTool(BlockMapping blockMapping) { + return requiresIronTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresDiamondTool(BlockMapping blockMapping) { + return requiresDiamondTool.contains(blockMapping.getJavaBlockId()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java deleted file mode 100644 index a114b8bbc6e..00000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.connector.network.session.cache; - -import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import lombok.Getter; - -import org.geysermc.common.window.FormWindow; -import org.geysermc.connector.network.session.GeyserSession; - -public class WindowCache { - - private final GeyserSession session; - - @Getter - private final Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); - - public WindowCache(GeyserSession session) { - this.session = session; - } - - public void addWindow(FormWindow window) { - windows.put(windows.size() + 1, window); - } - - public void addWindow(FormWindow window, int id) { - windows.put(id, window); - } - - public void showWindow(FormWindow window) { - showWindow(window, windows.size() + 1); - } - - public void showWindow(int id) { - if (!windows.containsKey(id)) - return; - - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(windows.get(id).getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - } - - public void showWindow(FormWindow window, int id) { - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(window.getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - - addWindow(window, id); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 84678c21133..4a293962120 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -28,7 +28,6 @@ import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; @@ -40,13 +39,6 @@ public class WorldCache { @Setter private Difficulty difficulty = Difficulty.EASY; - /** - * True if the client prefers being shown their coordinates, regardless if they're being shown or not. - * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. - */ - @Setter - private boolean prefersShowCoordinates = true; - private Scoreboard scoreboard; private final ScoreboardUpdater scoreboardUpdater; @@ -71,17 +63,4 @@ public int increaseAndGetScoreboardPacketsPerSecond() { int pps = scoreboardUpdater.getPacketsPerSecond(); return Math.max(pps, pendingPps); } - - /** - * Tell the client to hide or show the coordinates. - * - * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
- *
- * {@link GeyserSession#reducedDebugInfo} is enabled - * {@link GeyserConfiguration#isShowCoordinates()} is disabled - */ - public void updateShowCoordinates() { - boolean allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); - session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); - } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index eed901cdf6d..d797381cee1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -30,7 +30,6 @@ import com.nukkitx.nbt.NbtUtils; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; import java.util.Arrays; @@ -59,7 +58,7 @@ public static void init() { biomesTag = (NbtMap) biomenbtInputStream.readTag(); BIOMES = biomesTag; } catch (Exception ex) { - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.biome_read")); + GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); throw new AssertionError(ex); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java index fb6d5b93dec..5bb029882ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java @@ -29,7 +29,6 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; @@ -54,7 +53,7 @@ public static void init() { try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { ENTITY_IDENTIFIERS = (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.entity"), e); + throw new AssertionError("Unable to get entities from entity identifiers", e); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index 6f76326eeef..b1d01d51513 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -75,10 +75,10 @@ public class PacketTranslatorRegistry { BEDROCK_TRANSLATOR.translators.put(targetPacket, translator); } else { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.invalid_target", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index 9becfb36f45..493789bbb61 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -27,6 +27,8 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; +import org.geysermc.connector.entity.ItemFrameEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -43,9 +45,21 @@ public void translate(BlockPickRequestPacket packet, GeyserSession session) { // Block is air - chunk caching is probably off if (blockToPick == BlockTranslator.JAVA_AIR_ID) { + // Check for an item frame since the client thinks that's a block when it's an entity in Java + ItemFrameEntity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (entity != null) { + // Check to see if the item frame has an item in it first + if (entity.getHeldItem() != null && entity.getHeldItem().getId() != 0) { + // Grab the item in the frame + InventoryUtils.findOrCreateItem(session, entity.getHeldItem()); + } else { + // Grab the frame as the item + InventoryUtils.findOrCreateItem(session, entity.getEntityType() == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_item_frame" : "minecraft:item_frame"); + } + } return; } - InventoryUtils.findOrCreateItem(session, BlockTranslator.getPickItem(blockToPick)); + InventoryUtils.findOrCreateItem(session, BlockTranslator.getBlockMapping(blockToPick).getPickItem()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java index 7e2238f3322..2519aa44735 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; +import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -35,6 +36,10 @@ public class BedrockEmoteListTranslator extends PacketTranslator { + if (packet.getItemInHand() != null) { + // Otherwise boats will not be able to be placed in survival and buckets won't work on mobile + if (ItemRegistry.BOATS.contains(packet.getItemInHand().getId())) { ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); session.sendDownstreamPacket(itemPacket); - }, 5, TimeUnit.MILLISECONDS)); + } + // Check actions, otherwise buckets may be activated when block inventories are accessed + else if (ItemRegistry.BUCKETS.contains(packet.getItemInHand().getId())) { + // Let the server decide if the bucket item should change, not the client, and revert the changes the client made + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(packet.getHotbarSlot()); + slotPacket.setItem(packet.getItemInHand()); + session.sendUpstreamPacket(slotPacket); + // Delay the interaction in case the client doesn't intend to actually use the bucket + // See BedrockActionTranslator.java + session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> { + ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + }, 5, TimeUnit.MILLISECONDS)); + } } if (packet.getActions().isEmpty()) { @@ -238,15 +251,30 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. session.setInteracting(true); break; case 1: - // Handled in Entity.java + 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() == ItemRegistry.SHIELD.getJavaId()) { break; } - // Handled in ITEM_USE if the item is not milk - if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet.getItemInHand().getId()) && - packet.getItemInHand().getId() != ItemRegistry.MILK_BUCKET.getBedrockId()) { - break; + if (packet.getItemInHand() != null) { + if (ItemRegistry.BUCKETS.contains(packet.getItemInHand().getId()) && + packet.getItemInHand().getId() != ItemRegistry.MILK_BUCKET.getBedrockId()) { + // Handled in case 0 if the item is not milk + break; + } else if (ItemRegistry.SPAWN_EGGS.contains(packet.getItemInHand().getId())) { + // Handled in case 0 + break; + } } ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); @@ -279,9 +307,10 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. session.sendUpstreamPacket(blockBreakPacket); session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID); - long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()); - if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) { - ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking()); + Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (itemFrameEntity != null) { + ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) itemFrameEntity.getEntityId(), + InteractAction.ATTACK, session.isSneaking()); session.sendDownstreamPacket(attackPacket); break; } @@ -321,7 +350,7 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. session.sendUpstreamPacket(openPacket); break; } - Vector3f vector = packet.getClickPosition(); + Vector3f vector = packet.getClickPosition().sub(entity.getPosition()); ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), @@ -329,7 +358,7 @@ else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet. session.sendDownstreamPacket(interactPacket); session.sendDownstreamPacket(interactAtPacket); - EntitySoundInteractionHandler.handleEntityInteraction(session, vector, entity); + EntitySoundInteractionHandler.handleEntityInteraction(session, packet.getClickPosition(), entity); break; case 1: //Attack if (entity.getEntityType() == EntityType.ENDER_DRAGON) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java index 959d6dc291a..ac61f2f9db6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.nukkitx.protocol.bedrock.packet.ItemFrameDropItemPacket; +import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -44,9 +45,11 @@ public class BedrockItemFrameDropItemTranslator extends PacketTranslator { - ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapID); - if (mapPacket != null) { - session.sendUpstreamPacket(mapPacket); - } - }, 100, TimeUnit.MILLISECONDS); + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(mapPacket), + 100, TimeUnit.MILLISECONDS); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java index b053a204c6c..fbfaafb43f4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java @@ -45,9 +45,8 @@ public void translate(MoveEntityAbsolutePacket packet, GeyserSession session) { float y = packet.getPosition().getY(); if (session.getRidingVehicleEntity() instanceof BoatEntity) { - // Remove some Y position to prevents boats from looking like they're floating in water - // Not by the full boat offset because 1.16.100 complains and that's probably not good for the future - y -= (EntityType.BOAT.getOffset() - 0.5f); + // Remove the offset to prevents boats from looking like they're floating in water + y -= EntityType.BOAT.getOffset(); } ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket( packet.getPosition().getX(), y, packet.getPosition().getZ(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 56387fd5816..208e7c75d8a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -27,10 +27,17 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.Attribute; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.floodgate.util.DeviceOs; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; /** * Used to send the forwarded keep alive packet back to the server @@ -40,18 +47,38 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator 0) { + if (session.getConnector().getConfig().isForwardPlayerPing()) { + ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId); + session.sendDownstreamPacket(keepAlivePacket); } - session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId)); + return; } + + // Hack to fix the url image loading bug + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + + Attribute attribute = session.getPlayerEntity().getAttributes().get(AttributeType.EXPERIENCE_LEVEL); + if (attribute != null) { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(attribute))); + } else { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(AttributeType.EXPERIENCE_LEVEL.getAttribute(0)))); + } + + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(attributesPacket), + 500, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 3f322628097..501ed446808 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -31,21 +31,22 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.cumulus.CustomForm; import java.util.concurrent.TimeUnit; @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - @Override public void translate(ServerSettingsRequestPacket packet, GeyserSession session) { - SettingsUtils.buildForm(session); + CustomForm window = SettingsUtils.buildForm(session); + int windowId = session.getFormCache().addForm(window); // Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay session.getConnector().getGeneralThreadPool().schedule(() -> { ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); - serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData()); - serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID); + serverSettingsResponsePacket.setFormData(window.getJsonData()); + serverSettingsResponsePacket.setFormId(windowId); session.sendUpstreamPacket(serverSettingsResponsePacket); }, 1, TimeUnit.SECONDS); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 2f737b1c410..0bc158502c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.*; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.*; -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.LevelEventType; import com.nukkitx.protocol.bedrock.data.PlayerActionType; @@ -41,18 +41,14 @@ import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; -import java.util.concurrent.TimeUnit; - @Translator(packet = PlayerActionPacket.class) public class BedrockActionTranslator extends PacketTranslator { @@ -162,41 +158,31 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { // Otherwise handled in BedrockInventoryTransactionTranslator break; case START_BREAK: - if (session.getConnector().getConfig().isCacheChunks()) { - // Start the block breaking animation - if (session.getGameMode() != GameMode.CREATIVE) { - int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector); - double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState); - LevelEventPacket startBreak = new LevelEventPacket(); - startBreak.setType(LevelEventType.BLOCK_START_BREAK); - startBreak.setPosition(vector.toFloat()); - PlayerInventory inventory = session.getPlayerInventory(); - GeyserItemStack item = inventory.getItemInHand(); - ItemEntry itemEntry = null; - CompoundTag nbtData = new CompoundTag(""); - if (item != null) { - itemEntry = item.getItemEntry(); - nbtData = item.getNbt(); - } - double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, blockState, itemEntry, nbtData, session) * 20); - startBreak.setData((int) (65535 / breakTime)); - session.setBreakingBlock(blockState); - session.sendUpstreamPacket(startBreak); - } + // Start the block breaking animation + if (session.getGameMode() != GameMode.CREATIVE) { + int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector); + LevelEventPacket startBreak = new LevelEventPacket(); + startBreak.setType(LevelEventType.BLOCK_START_BREAK); + startBreak.setPosition(vector.toFloat()); + double breakTime = BlockUtils.getSessionBreakTime(session, BlockTranslator.getBlockMapping(blockState)) * 20; + startBreak.setData((int) (65535 / breakTime)); + session.setBreakingBlock(blockState); + session.sendUpstreamPacket(startBreak); + } - // Account for fire - the client likes to hit the block behind. - Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace()); - int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos); - String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp); - if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) { - ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(), - fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]); - session.sendDownstreamPacket(startBreakingPacket); - if (session.getGameMode() == GameMode.CREATIVE) { - break; - } + // Account for fire - the client likes to hit the block behind. + Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace()); + int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos); + String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp); + if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) { + ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(), + fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]); + session.sendDownstreamPacket(startBreakingPacket); + if (session.getGameMode() == GameMode.CREATIVE) { + break; } } + ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, BlockFace.values()[packet.getFace()]); session.sendDownstreamPacket(startBreakingPacket); break; @@ -204,19 +190,28 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { if (session.getGameMode() == GameMode.CREATIVE) { break; } + Vector3f vectorFloat = vector.toFloat(); LevelEventPacket continueBreakPacket = new LevelEventPacket(); continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK); continueBreakPacket.setData((session.getBlockTranslator().getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24)); - continueBreakPacket.setPosition(vector.toFloat()); + continueBreakPacket.setPosition(vectorFloat); session.sendUpstreamPacket(continueBreakPacket); + + // Update the break time in the event that player conditions changed (jumping, effects applied) + LevelEventPacket updateBreak = new LevelEventPacket(); + updateBreak.setType(LevelEventType.BLOCK_UPDATE_BREAK); + updateBreak.setPosition(vectorFloat); + double breakTime = BlockUtils.getSessionBreakTime(session, BlockTranslator.getBlockMapping(session.getBreakingBlock())) * 20; + updateBreak.setData((int) (65535 / breakTime)); + session.sendUpstreamPacket(updateBreak); break; case ABORT_BREAK: if (session.getGameMode() != GameMode.CREATIVE) { // As of 1.16.210: item frame items are taken out here. // Survival also sends START_BREAK, but by attaching our process here adventure mode also works - long entityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()); - if (entityId != -1) { - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entityId, + Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (itemFrameEntity != null) { + ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) itemFrameEntity.getEntityId(), InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); session.sendDownstreamPacket(interactPacket); break; @@ -244,11 +239,7 @@ public void translate(PlayerActionPacket packet, GeyserSession session) { session.getEntityCache().updateBossBars(); break; case JUMP: - if (!session.getConnector().getConfig().isCacheChunks()) { - // Save the jumping status for determining teleport status - session.setJumping(true); - session.getConnector().getGeneralThreadPool().schedule(() -> session.setJumping(false), 1, TimeUnit.SECONDS); - } + entity.setOnGround(false); // Increase block break time while jumping break; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java index a9e911ac23d..fb7c7a14745 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java @@ -25,25 +25,43 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.nukkitx.protocol.bedrock.packet.EmotePacket; +import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.BlockUtils; @Translator(packet = EmotePacket.class) public class BedrockEmoteTranslator extends PacketTranslator { @Override public void translate(EmotePacket packet, GeyserSession session) { + if (session.getConnector().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) { + // Activate the workaround - we should trigger the offhand now + ClientPlayerActionPacket swapHandsPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO, + BlockFace.DOWN); + session.sendDownstreamPacket(swapHandsPacket); + + if (session.getConnector().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { + return; + } + } + long javaId = session.getPlayerEntity().getEntityId(); for (GeyserSession otherSession : session.getConnector().getPlayers()) { if (otherSession != session) { if (otherSession.isClosed()) continue; Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); if (otherEntity == null) continue; - packet.setRuntimeEntityId(otherEntity.getGeyserId()); - otherSession.sendUpstreamPacket(packet); + EmotePacket otherEmotePacket = new EmotePacket(); + otherEmotePacket.setEmoteId(packet.getEmoteId()); + otherEmotePacket.setRuntimeEntityId(otherEntity.getGeyserId()); + otherSession.sendUpstreamPacket(otherEmotePacket); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java index 95bf7b3deb4..a003fc9121a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.connector.utils.LocaleUtils; +import javax.annotation.Nonnull; import java.text.MessageFormat; import java.util.Locale; import java.util.regex.Matcher; @@ -42,7 +43,7 @@ */ public class MinecraftTranslationRegistry implements Translator { @Override - public @NonNull Key name() { + public @Nonnull Key name() { return Key.key("geyser", "minecraft_translations"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java index ff95593bca1..b6d0ae176c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java @@ -141,32 +141,22 @@ public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, Double.parseDouble(Float.toString(bedrockPosition.getZ()))); - if (session.getConnector().getConfig().isCacheChunks()) { - // With chunk caching, we can do some proper collision checks - updatePlayerBoundingBox(position); - - // Correct player position - if (!correctPlayerPosition()) { - // Cancel the movement if it needs to be cancelled - recalculatePosition(); - return null; - } + updatePlayerBoundingBox(position); - position = Vector3d.from(playerBoundingBox.getMiddleX(), - playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), - playerBoundingBox.getMiddleZ()); + // Correct player position + if (!correctPlayerPosition()) { + // Cancel the movement if it needs to be cancelled + recalculatePosition(); + return null; + } - if (!onGround) { - // Trim the position to prevent rounding errors that make Java think we are clipping into a block - position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); - } - } else { - // When chunk caching is off, we have to rely on this - // It rounds the Y position up to the nearest 0.5 - // This snaps players to snap to the top of stairs and slabs like on Java Edition - // However, it causes issues such as the player floating on carpets - if (onGround) javaY = Math.ceil(javaY * 2) / 2; - position = position.up(javaY - position.getY()); + position = Vector3d.from(playerBoundingBox.getMiddleX(), + playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), + playerBoundingBox.getMiddleZ()); + + if (!onGround) { + // Trim the position to prevent rounding errors that make Java think we are clipping into a block + position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); } return position; @@ -268,10 +258,6 @@ public boolean correctPlayerPosition() { * were they not sneaking */ public boolean isUnderSlab() { - if (!session.getConnector().getConfig().isCacheChunks()) { - // We can't reliably determine this - return false; - } Vector3i position = session.getPlayerEntity().getPosition().toInt(); BlockCollision collision = CollisionTranslator.getCollisionAt(session, position.getX(), position.getY(), position.getZ()); if (collision != null) { @@ -289,8 +275,7 @@ public boolean isUnderSlab() { * @return if the player is currently in a water block */ public boolean isPlayerInWater() { - return session.getConnector().getConfig().isCacheChunks() - && session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockTranslator.JAVA_WATER_ID; + return session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockTranslator.JAVA_WATER_ID; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java index aa0180caa6d..e582d095919 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.collision; -import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java index ad74371d217..e5cf52bc8fa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java @@ -48,11 +48,6 @@ public class CollisionTranslator { private static final Int2ObjectMap COLLISION_MAP = new Int2ObjectOpenHashMap<>(); public static void init() { - // If chunk caching is off then don't initialize - if (!GeyserConnector.getInstance().getConfig().isCacheChunks()) { - return; - } - List> collisionTypes = new ArrayList<>(); Map, CollisionRemapper> annotationMap = new HashMap<>(); @@ -96,7 +91,7 @@ private static BlockCollision instantiateCollision(String blockID, int numericBl if (blockID.contains("[")) { params = "[" + blockID.split("\\[")[1]; } - int collisionIndex = BlockTranslator.JAVA_RUNTIME_ID_TO_COLLISION_INDEX.get(numericBlockID); + int collisionIndex = BlockTranslator.getBlockMapping(numericBlockID).getCollisionIndex(); for (Class type : collisionTypes) { CollisionRemapper annotation = annotationMap.get(type); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java index c71aa4f1dfa..91b21fb2ae8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java @@ -32,10 +32,9 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.NonNull; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FileUtils; import java.io.InputStream; @@ -51,11 +50,6 @@ public class EffectRegistry { public static final Map SOUND_EFFECTS = new HashMap<>(); public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); - /** - * Java particle type to Bedrock particle ID - * Used for area effect clouds. - */ - private static final Object2IntMap PARTICLE_TO_ID = new Object2IntOpenHashMap<>(); /** * Java particle type to Bedrock level event */ @@ -84,11 +78,7 @@ public static void init() { while (particlesIterator.hasNext()) { Map.Entry entry = particlesIterator.next(); JsonNode bedrockId = entry.getValue().get("bedrockId"); - JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId"); JsonNode eventType = entry.getValue().get("eventType"); - if (bedrockIdNumeric != null) { - PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt()); - } if (bedrockId != null) { PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText()); } @@ -137,20 +127,15 @@ public static void init() { javaEffect = SoundEffect.valueOf(entry.getKey()); String name = node.get("name").asText(); float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f; - boolean pitchSub = node.has("pitch_sub") ? node.get("pitch_sub").booleanValue() : false; + boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue(); float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f; float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f; - boolean relative = node.has("relative") ? node.get("relative").booleanValue() : true; + boolean relative = node.has("relative") && node.get("relative").booleanValue(); effect = new PlaySoundEffect(name, volume, pitchSub, pitchMul, pitchAdd, relative); break; } case "record": { - JsonNode records = entry.getValue().get("records"); - Iterator> recordsIterator = records.fields(); - while (recordsIterator.hasNext()) { - Map.Entry recordEntry = recordsIterator.next(); - RECORDS.put(Integer.parseInt(recordEntry.getKey()), SoundEvent.valueOf(recordEntry.getValue().asText())); - } + // Special case handled in ItemRegistry break; } } @@ -164,11 +149,19 @@ public static void init() { } /** + * Used for area effect clouds. + * * @param type the Java particle to search for * @return the Bedrock integer ID of the particle, or -1 if it does not exist */ - public static int getParticleId(@NonNull ParticleType type) { - return PARTICLE_TO_ID.getOrDefault(type, -1); + public static int getParticleId(GeyserSession session, @NonNull ParticleType type) { + LevelEventType levelEventType = getParticleLevelEventType(type); + if (levelEventType == null) { + return -1; + } + + // Remove the legacy bit applied to particles for LevelEventType serialization + return session.getUpstream().getSession().getPacketCodec().getHelper().getLevelEventId(levelEventType) & ~0x4000; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 85f16f2f6d1..668c9824769 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -59,6 +59,7 @@ import org.geysermc.connector.network.translators.inventory.translators.furnace.FurnaceInventoryTranslator; import org.geysermc.connector.network.translators.inventory.translators.furnace.SmokerInventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.ItemUtils; import java.util.*; @@ -868,7 +869,7 @@ public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, G if (itemStack.getNbt() != null) { Tag damage = itemStack.getNbt().get("Damage"); if (damage instanceof IntTag) { - durability = ((IntTag) damage).getValue(); + durability = ItemUtils.getCorrectBedrockDurability(itemStack.getJavaId(), ((IntTag) damage).getValue()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index c750baf51d8..5204bf6754e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -27,7 +27,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.WindowAction; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -43,7 +42,9 @@ import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; public class ClickPlan { private final List plan = new ArrayList<>(); @@ -116,22 +117,23 @@ public void execute(boolean refresh) { clickedItemStack = getItem(action.slot).getItemStack(); } - short actionId = inventory.getNextTransactionId(); + Int2ObjectMap affectedSlots = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { + affectedSlots.put(simulatedSlot.getIntKey(), simulatedSlot.getValue().getItemStack()); + } + ClientWindowActionPacket clickPacket = new ClientWindowActionPacket( inventory.getId(), - actionId, action.slot, - clickedItemStack, action.click.windowAction, - action.click.actionParam + action.click.actionParam, + clickedItemStack, + affectedSlots ); simulateAction(action); session.sendDownstreamPacket(clickPacket); - if (clickedItemStack == InventoryUtils.REFRESH_ITEM || action.force) { - session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); - } } session.getPlayerInventory().setCursor(simulatedCursor, session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 38a0935e6c9..1f36eaabe3f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -26,21 +26,14 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.*; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; -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.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import org.geysermc.connector.inventory.AnvilContainer; -import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; -import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { @@ -48,55 +41,6 @@ public AnvilInventoryTranslator() { "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } - /* 1.16.100 support start */ - @Override - @Deprecated - public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { - return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED; - } - - @Override - @Deprecated - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - if (!(request.getActions()[1] instanceof CraftResultsDeprecatedStackRequestActionData)) { - // Just silently log an error - session.getConnector().getLogger().debug("Something isn't quite right with taking an item out of an anvil."); - return translateRequest(session, inventory, request); - } - CraftResultsDeprecatedStackRequestActionData actionData = (CraftResultsDeprecatedStackRequestActionData) request.getActions()[1]; - ItemData resultItem = actionData.getResultItems()[0]; - if (resultItem.getTag() != null) { - NbtMap displayTag = resultItem.getTag().getCompound("display"); - if (displayTag != null && displayTag.containsKey("Name")) { - ItemData sourceSlot = inventory.getItem(0).getItemData(session); - - if (sourceSlot.getTag() != null) { - NbtMap oldDisplayTag = sourceSlot.getTag().getCompound("display"); - if (oldDisplayTag != null && oldDisplayTag.containsKey("Name")) { - if (!displayTag.getString("Name").equals(oldDisplayTag.getString("Name"))) { - // Name has changed - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } else { - // No display tag on the old item - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } else { - // New NBT tag - sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); - } - } - } - return translateRequest(session, inventory, request); - } - - private void sendRenamePacket(GeyserSession session, Inventory inventory, ItemData outputItem, String name) { - session.sendDownstreamPacket(new ClientRenameItemPacket(name)); - inventory.setItem(2, GeyserItemStack.from(ItemTranslator.translateToJava(outputItem)), session); - } - - /* 1.16.100 support end */ - @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { switch (slotInfoData.getContainer()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 5af921f2d5d..1d40db51f77 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -54,15 +54,6 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public BeaconInventoryTranslator() { super(1, new BlockInventoryHolder("minecraft:beacon", ContainerType.BEACON) { - @Override - public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - if (!session.getConnector().getConfig().isCacheChunks()) { - // Beacons cannot work without knowing their physical location - return; - } - super.prepareInventory(translator, session, inventory); - } - @Override protected boolean checkInteractionPosition(GeyserSession session) { // Since we can't fall back to a virtual inventory, let's make opening one easier @@ -71,7 +62,7 @@ protected boolean checkInteractionPosition(GeyserSession session) { @Override public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - if (!session.getConnector().getConfig().isCacheChunks() || !((BeaconContainer) inventory).isUsingRealBlock()) { + if (!((BeaconContainer) inventory).isUsingRealBlock()) { InventoryUtils.closeInventory(session, inventory.getId(), false); return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index 6e03c7df351..49b582cef51 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -72,7 +72,7 @@ public void updateProperty(GeyserSession session, Inventory inventory, int key, // The Bedrock index might need changed, so let's look it up and see. int bedrockIndex = value; if (bedrockIndex != -1) { - Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[bedrockIndex].name().toLowerCase()); + Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + Enchantment.JavaEnchantment.of(bedrockIndex).name().toLowerCase()); if (enchantment != null) { // Convert the Java enchantment index to Bedrock's bedrockIndex = enchantment.ordinal(); @@ -170,48 +170,4 @@ public int javaSlotToBedrock(int slot) { public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { return new EnchantingContainer(name, windowId, this.size, windowType, playerInventory); } - - /** - * Enchantments classified by their Java index - */ - public enum JavaEnchantment { - PROTECTION, - FIRE_PROTECTION, - FEATHER_FALLING, - BLAST_PROTECTION, - PROJECTILE_PROTECTION, - RESPIRATION, - AQUA_AFFINITY, - THORNS, - DEPTH_STRIDER, - FROST_WALKER, - BINDING_CURSE, - SOUL_SPEED, - SHARPNESS, - SMITE, - BANE_OF_ARTHROPODS, - KNOCKBACK, - FIRE_ASPECT, - LOOTING, - SWEEPING, - EFFICIENCY, - SILK_TOUCH, - UNBREAKING, - FORTUNE, - POWER, - PUNCH, - FLAME, - INFINITY, - LUCK_OF_THE_SEA, - LURE, - LOYALTY, - IMPALING, - RIPTIDE, - CHANNELING, - MULTISHOT, - QUICK_CHARGE, - PIERCING, - MENDING, - VANISHING_CURSE - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index c08dfd995c7..b15a7349380 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -78,71 +78,81 @@ public void updateProperty(GeyserSession session, Inventory inventory, int key, @Override public void updateInventory(GeyserSession session, Inventory inventory) { + GeyserItemStack itemStack = inventory.getItem(0); + if (!itemStack.isEmpty()) { + updateBook(session, inventory, itemStack); + } } @Override public void updateSlot(GeyserSession session, Inventory inventory, int slot) { this.updater.updateSlot(this, session, inventory, slot); if (slot == 0) { - LecternContainer lecternContainer = (LecternContainer) inventory; - if (session.isDroppingLecternBook()) { - // We have to enter the inventory GUI to eject the book - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3); - session.sendDownstreamPacket(packet); - session.setDroppingLecternBook(false); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } else if (lecternContainer.getBlockEntityTag() == null) { - // If the method returns true, this is already handled for us - GeyserItemStack geyserItemStack = inventory.getItem(0); - CompoundTag tag = geyserItemStack.getNbt(); - // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionBlockPosition(); - // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet - boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); - - NbtMap blockEntityTag; - if (tag != null) { - int pagesSize = ((ListTag) tag.get("pages")).size(); - ItemData itemData = geyserItemStack.getItemData(session); - NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); - lecternTag.putCompound("book", NbtMap.builder() - .putByte("Count", (byte) itemData.getCount()) - .putShort("Damage", (short) 0) - .putString("Name", "minecraft:written_book") - .putCompound("tag", itemData.getTag()) - .build()); - lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); - blockEntityTag = lecternTag.build(); - } else { - // There is *a* book here, but... no NBT. - NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); - NbtMapBuilder bookTag = NbtMap.builder() - .putByte("Count", (byte) 1) - .putShort("Damage", (short) 0) - .putString("Name", "minecraft:writable_book") - .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( - NbtMap.builder() + updateBook(session, inventory, inventory.getItem(0)); + } + } + + /** + * Translate the data of the book in the lectern into a block entity tag. + */ + private void updateBook(GeyserSession session, Inventory inventory, GeyserItemStack book) { + LecternContainer lecternContainer = (LecternContainer) inventory; + if (session.isDroppingLecternBook()) { + // We have to enter the inventory GUI to eject the book + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3); + session.sendDownstreamPacket(packet); + session.setDroppingLecternBook(false); + InventoryUtils.closeInventory(session, inventory.getId(), false); + } else if (lecternContainer.getBlockEntityTag() == null) { + CompoundTag tag = book.getNbt(); + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionBlockPosition(); + // If shouldExpectLecternHandled returns true, this is already handled for us + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet + boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + + NbtMap blockEntityTag; + if (tag != null) { + int pagesSize = ((ListTag) tag.get("pages")).size(); + ItemData itemData = book.getItemData(session); + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) itemData.getCount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", itemData.getTag()) + .build()); + lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); + blockEntityTag = lecternTag.build(); + } else { + // There is *a* book here, but... no NBT. + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book") + .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( + NbtMap.builder() .putString("photoname", "") .putString("text", "") - .build() - )).build()); - - blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); - } - - // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild - // the block entity tag - lecternContainer.setBlockEntityTag(blockEntityTag); - lecternContainer.setPosition(position); - if (shouldRefresh) { - // Update the lectern because it's not updated client-side - BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); - session.getLecternCache().add(position); - // Close the window - we will reopen it once the client has this data synced - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); - session.sendDownstreamPacket(closeWindowPacket); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } + .build() + )).build()); + + blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); + } + + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId(), false); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java index a3b4b6c3193..14b918a4f7e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java @@ -69,17 +69,7 @@ public enum Enchantment { QUICK_CHARGE, SOUL_SPEED; - /** - * A list of all enchantment Java identifiers for use with command suggestions. - */ - public static final String[] ALL_JAVA_IDENTIFIERS; - - static { - ALL_JAVA_IDENTIFIERS = new String[values().length]; - for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { - ALL_JAVA_IDENTIFIERS[i] = values()[i].javaIdentifier; - } - } + private static final Enchantment[] VALUES = values(); private final String javaIdentifier; @@ -88,7 +78,7 @@ public enum Enchantment { } public static Enchantment getByJavaIdentifier(String javaIdentifier) { - for (Enchantment enchantment : Enchantment.values()) { + for (Enchantment enchantment : VALUES) { if (enchantment.javaIdentifier.equals(javaIdentifier) || enchantment.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(javaIdentifier)) { return enchantment; } @@ -97,9 +87,71 @@ public static Enchantment getByJavaIdentifier(String javaIdentifier) { } public static Enchantment getByBedrockId(int bedrockId) { - if (bedrockId >= 0 && bedrockId < Enchantment.values().length) { - return Enchantment.values()[bedrockId]; + if (bedrockId >= 0 && bedrockId < VALUES.length) { + return VALUES[bedrockId]; } return null; } + + /** + * Enchantments classified by their Java index + */ + public enum JavaEnchantment { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + RESPIRATION, + AQUA_AFFINITY, + THORNS, + DEPTH_STRIDER, + FROST_WALKER, + BINDING_CURSE, + SOUL_SPEED, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + SWEEPING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + LOYALTY, + IMPALING, + RIPTIDE, + CHANNELING, + MULTISHOT, + QUICK_CHARGE, + PIERCING, + MENDING, + VANISHING_CURSE; + + private static final JavaEnchantment[] VALUES = JavaEnchantment.values(); + + public static JavaEnchantment of(int index) { + return VALUES[index]; + } + + /** + * A list of all enchantment Java identifiers for use with command suggestions. + */ + public static final String[] ALL_JAVA_IDENTIFIERS; + + static { + ALL_JAVA_IDENTIFIERS = new String[VALUES.length]; + for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { + ALL_JAVA_IDENTIFIERS[i] = "minecraft:" + VALUES[i].name().toLowerCase(Locale.ENGLISH); + } + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index 2ac203e8721..74ce0951fa1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -28,15 +28,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; @Getter @AllArgsConstructor @ToString public class ItemEntry { - - public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, - BlockTranslator1_16_210.INSTANCE.getBedrockAirId(), 64); + public static final ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, + BlockTranslator1_17_0.INSTANCE.getBedrockAirId(), 64); private final String javaIdentifier; private final String bedrockIdentifier; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 7550bc818a4..4d8a50e85d1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -33,21 +33,19 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.nbt.NbtUtils; +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 it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntArraySet; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_17_0; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -100,6 +98,10 @@ public class ItemRegistry { * Crossbow item entry, used in PillagerEntity.java */ public static ItemEntry CROSSBOW; + /** + * Fishing rod item entry, used in ItemUtils.java + */ + public static ItemEntry FISHING_ROD; /** * Empty item bucket, used in BedrockInventoryTransactionTranslator.java */ @@ -108,14 +110,14 @@ public class ItemRegistry { * Egg item entry, used in JavaEntityStatusTranslator.java */ public static ItemEntry EGG; - /** - * Gold item entry, used in PiglinEntity.java - */ - public static ItemEntry GOLD; /** * Shield item entry, used in Entity.java and LivingEntity.java */ public static ItemEntry SHIELD; + /** + * A list of all spawn eggs by their Bedrock IDs. Used in BedrockInventoryTransactionTranslator.java + */ + public static final IntSet SPAWN_EGGS = new IntArraySet(); /** * Wheat item entry, used in AbstractHorseEntity.java */ @@ -152,7 +154,7 @@ public static void init() { try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); + throw new AssertionError("Unable to load Bedrock runtime item IDs", e); } int lodestoneCompassId = 0; @@ -176,7 +178,7 @@ public static void init() { try { creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.creative"), e); + throw new AssertionError("Unable to load creative items", e); } int netId = 1; @@ -251,10 +253,10 @@ public static void init() { try { items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + throw new AssertionError("Unable to load Java runtime item IDs", e); } - BlockTranslator blockTranslator = BlockTranslator1_16_210.INSTANCE; + BlockTranslator blockTranslator = BlockTranslator1_17_0.INSTANCE; int itemIndex = 0; int javaFurnaceMinecartId = 0; @@ -294,7 +296,7 @@ public static void init() { // However, in order for some visuals and crafting to work, we need to send the first matching block state // as indexed by Bedrock's block palette // There are exceptions! But, ideally, the block ID override should take care of those. - String javaBlockIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockRuntimeIdNode.intValue()).split("\\[")[0]; + String javaBlockIdentifier = BlockTranslator.getBlockMapping(blockRuntimeIdNode.intValue()).getCleanJavaIdentifier(); NbtMapBuilder requiredBlockStatesBuilder = NbtMap.builder(); String correctBedrockIdentifier = blockTranslator.getAllBedrockBlockStates().get(aValidBedrockBlockId).getString("name"); boolean firstPass = true; @@ -408,6 +410,16 @@ public static void init() { "", bedrockBlockId, stackSize); } + } else if (entry.getKey().equals("minecraft:spectral_arrow") || entry.getKey().equals("minecraft:knowledge_book") + // To remove later... hopefully + || entry.getKey().contains("candle") || entry.getKey().equals("minecraft:bundle") || entry.getKey().equals("minecraft:sculk_sensor")) { + // These items don't exist on Bedrock, so set up a container that indicates they should have custom names + itemEntry = new TranslatableItemEntry( + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, + entry.getValue().get("bedrock_data").intValue(), + bedrockBlockId, + stackSize); + GeyserConnector.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); } else { itemEntry = new ItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, @@ -430,8 +442,8 @@ public static void init() { case "minecraft:egg": EGG = itemEntry; break; - case "minecraft:gold_ingot": - GOLD = itemEntry; + case "minecraft:fishing_rod": + FISHING_ROD = itemEntry; break; case "minecraft:shield": SHIELD = itemEntry; @@ -453,16 +465,22 @@ public static void init() { } if (entry.getKey().contains("boat")) { - BOATS.add(entry.getValue().get("bedrock_id").intValue()); + BOATS.add(itemEntry.getBedrockId()); } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { - BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); - } else if (entry.getKey().contains("_carpet")) { + BUCKETS.add(itemEntry.getBedrockId()); + } else if (entry.getKey().contains("_carpet") && !entry.getKey().contains("moss")) { // This should be the numerical order Java sends as an integer value for llamas CARPETS.add(ItemData.builder() .id(itemEntry.getBedrockId()) .damage(itemEntry.getBedrockData()) .count(1) .blockRuntimeId(itemEntry.getBedrockBlockId()).build()); + } else if (entry.getKey().startsWith("minecraft:music_disc_")) { + // The Java record level event uses the item ID as the "key" to play the record + EffectRegistry.RECORDS.put(itemIndex, SoundEvent.valueOf("RECORD_" + + entry.getKey().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH))); + } else if (entry.getKey().endsWith("_spawn_egg")) { + SPAWN_EGGS.add(itemEntry.getBedrockId()); } itemNames.add(entry.getKey()); @@ -471,7 +489,6 @@ public static void init() { } itemNames.add("minecraft:furnace_minecart"); - itemNames.add("minecraft:spectral_arrow"); if (lodestoneCompassId == 0) { throw new RuntimeException("Lodestone compass not found in item palette!"); @@ -531,44 +548,19 @@ public static void init() { Set javaOnlyItems = new ObjectOpenHashSet<>(); Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book", "minecraft:tipped_arrow"); + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg", + // To be removed in Bedrock 1.17.10... right??? RIGHT??? + "minecraft:candle", "minecraft:white_candle", "minecraft:orange_candle", "minecraft:magenta_candle", + "minecraft:light_blue_candle", "minecraft:yellow_candle", "minecraft:lime_candle", "minecraft:pink_candle", + "minecraft:gray_candle", "minecraft:light_gray_candle", "minecraft:cyan_candle", "minecraft:purple_candle", + "minecraft:blue_candle", "minecraft:brown_candle", "minecraft:green_candle", "minecraft:red_candle", "minecraft:black_candle", + "minecraft:bundle", "minecraft:sculk_sensor"); if (!usingFurnaceMinecart) { javaOnlyItems.add("minecraft:furnace_minecart"); } JAVA_ONLY_ITEMS = ImmutableSet.copyOf(javaOnlyItems); } - /* pre-1.16.220 support start */ - - private static ItemData[] LEGACY_CREATIVE_CONTENTS = null; - - /** - * Built on the fly so extra memory isn't used if there are no 1.16.210-or-below clients joining. - * - * @return a list of creative items built for versions before 1.16.220. - */ - public static ItemData[] getPre1_16_220CreativeContents() { - if (LEGACY_CREATIVE_CONTENTS != null) { - return LEGACY_CREATIVE_CONTENTS; - } - - // Pre-1.16.220 relies on item damage values that the creative content packet drops - ItemData[] creativeContents = new ItemData[CREATIVE_ITEMS.length]; - for (int i = 0; i < CREATIVE_ITEMS.length; i++) { - ItemData item = CREATIVE_ITEMS[i]; - if (item.getBlockRuntimeId() != 0) { - creativeContents[i] = item.toBuilder().damage(getItem(item).getBedrockData()).build(); - } else { - // No block runtime ID means that this item is backwards-compatible - creativeContents[i] = item; - } - } - LEGACY_CREATIVE_CONTENTS = creativeContents; - return creativeContents; - } - - /* pre-1.16.220 support end */ - /** * Gets an {@link ItemEntry} from the given {@link ItemStack}. * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 10460224c65..34483db13f9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -41,7 +41,7 @@ import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.connector.utils.LocaleUtils; import org.reflections.Reflections; import java.util.*; @@ -83,13 +83,15 @@ public static void init() { for (ItemEntry item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); if (registered != null) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.already_registered", clazz.getCanonicalName(), registered.getClass().getCanonicalName(), item.getJavaIdentifier())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + + clazz.getCanonicalName() + ". Item translator " + registered.getClass().getCanonicalName() + + " is already registered for the item " + item.getJavaIdentifier()); continue; } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName()); } } @@ -143,8 +145,6 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack nbt.put(new IntTag("map", 0)); } - ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); - if (nbt != null) { for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(bedrockItem)) { @@ -153,7 +153,9 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack } } - translateDisplayProperties(session, nbt); + nbt = translateDisplayProperties(session, nbt, bedrockItem); + + ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); ItemData.Builder builder; ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); @@ -230,14 +232,14 @@ public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemEntry itemEn public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { if (itemData == null) return null; if (itemData.getTag() == null) { - return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new com.github.steveice10.opennbt.tag.builtin.CompoundTag("")); + return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new CompoundTag("")); } return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag())); } public abstract List getAppliedItems(); - public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { + public NbtMap translateNbtToBedrock(CompoundTag tag) { NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { @@ -252,7 +254,7 @@ public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.Co return builder.build(); } - private Object translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { + private Object translateToBedrockNBT(Tag tag) { if (tag instanceof ByteArrayTag) { return ((ByteArrayTag) tag).getValue(); } @@ -300,7 +302,7 @@ private Object translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.T ListTag listTag = (ListTag) tag; List tagList = new ArrayList<>(); - for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { + for (Tag value : listTag) { tagList.add(translateToBedrockNBT(value)); } NbtType type = NbtType.COMPOUND; @@ -399,8 +401,20 @@ private Tag translateToJavaNBT(String name, Object object) { * Translates the display name of the item * @param session the Bedrock client's session * @param tag the tag to translate + * @param itemEntry the item entry, in case it requires translation + * + * @return the new tag to use, should the current one be null + */ + public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemEntry itemEntry) { + return translateDisplayProperties(session, tag, itemEntry, 'f'); + } + + /** + * @param translationColor if this item is not available on Java, the color that the new name should be. + * Normally, this should just be white, but for shulker boxes this should be gray. */ - public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { + public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemEntry itemEntry, char translationColor) { + boolean hasCustomName = false; if (tag != null) { CompoundTag display = tag.get("display"); if (display != null && display.contains("Name")) { @@ -411,11 +425,32 @@ public static void translateDisplayProperties(GeyserSession session, CompoundTag // Add the new name tag display.put(new StringTag("Name", name)); + // Indicate that a custom name is present + hasCustomName = true; // Add to the new root tag tag.put(display); } } + + if (!hasCustomName && itemEntry instanceof TranslatableItemEntry) { + // No custom name, but we need to localize the item's name + if (tag == null) { + tag = new CompoundTag(""); + } + CompoundTag display = tag.get("display"); + if (display == null) { + display = new CompoundTag("display"); + // Add to the new root tag + tag.put(display); + } + + String translationKey = ((TranslatableItemEntry) itemEntry).getTranslationString(); + // Reset formatting since Bedrock defaults to italics + display.put(new StringTag("Name", "§r§" + translationColor + LocaleUtils.getLocaleString(translationKey, session.getLocale()))); + } + + return tag; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java index 4e710c55091..57f8c756c9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java @@ -74,6 +74,8 @@ public enum Potion { SLOW_FALLING(40), LONG_SLOW_FALLING(41); + public static final Potion[] VALUES = values(); + private final String javaIdentifier; private final short bedrockId; @@ -83,7 +85,7 @@ public enum Potion { } public static Potion getByJavaIdentifier(String javaIdentifier) { - for (Potion potion : Potion.values()) { + for (Potion potion : VALUES) { if (potion.javaIdentifier.equals(javaIdentifier)) { return potion; } @@ -92,7 +94,7 @@ public static Potion getByJavaIdentifier(String javaIdentifier) { } public static Potion getByBedrockId(int bedrockId) { - for (Potion potion : Potion.values()) { + for (Potion potion : VALUES) { if (potion.bedrockId == bedrockId) { return potion; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java index 16cbc54ac36..30be7da23c7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java @@ -80,7 +80,7 @@ public static void init() { // Add all types of potions as inputs ItemEntry fillerIngredient = ingredients.get(0); for (ItemEntry input : inputs) { - for (Potion potion : Potion.values()) { + for (Potion potion : Potion.VALUES) { potionMixes.add(new PotionMixData( input.getBedrockId(), potion.getBedrockId(), fillerIngredient.getBedrockId(), fillerIngredient.getBedrockData(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 8f7a4a8a24d..506317ab96b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -41,7 +41,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -144,7 +143,7 @@ public class RecipeRegistry { try { items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + throw new AssertionError("Unable to load Java runtime item IDs", e); } for (JsonNode entry : items.get("leather_armor")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java index 5aac0979028..312de593c9e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java @@ -77,6 +77,8 @@ public enum TippedArrowPotion { SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); + private static final TippedArrowPotion[] VALUES = values(); + private final String javaIdentifier; private final short bedrockId; /** @@ -92,7 +94,7 @@ public enum TippedArrowPotion { } public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + for (TippedArrowPotion potion : VALUES) { if (potion.javaIdentifier.equals(javaIdentifier)) { return potion; } @@ -101,7 +103,7 @@ public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { } public static TippedArrowPotion getByBedrockId(int bedrockId) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + for (TippedArrowPotion potion : VALUES) { if (potion.bedrockId == bedrockId) { return potion; } @@ -114,7 +116,7 @@ public static TippedArrowPotion getByBedrockId(int bedrockId) { * @return the tipped arrow potion that most closely resembles that color. */ public static TippedArrowPotion getByJavaColor(int color) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + for (TippedArrowPotion potion : VALUES) { if (potion.javaColor == color) { return potion; } diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java similarity index 66% rename from common/src/main/java/org/geysermc/common/window/button/FormImage.java rename to connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java index 03427829172..3967abb1798 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/TranslatableItemEntry.java @@ -23,40 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.connector.network.translators.item; import lombok.Getter; -import lombok.Setter; - -public class FormImage { - - @Getter - @Setter - private String type; +/** + * Used when an item should have a custom name applied, if there already isn't one. + */ +public class TranslatableItemEntry extends ItemEntry { @Getter - @Setter - private String data; - - public FormImage(FormImageType type, String data) { - this.type = type.getName(); - this.data = data; - } - - public enum FormImageType { - PATH("path"), - URL("url"); - - @Getter - private String name; - - FormImageType(String name) { - this.name = name; - } + private final String translationString; - @Override - public String toString() { - return name; - } + public TranslatableItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, int bedrockBlockId, int stackSize) { + super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, bedrockBlockId, stackSize); + this.translationString = (isBlock() ? "block." : "item.") + javaIdentifier.replace(":", "."); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index b127e1928c2..fb1256e40c7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -131,8 +131,8 @@ public static NbtMap getBedrockBannerPattern(CompoundTag pattern) { */ public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); - for (Object patternTag : patterns) { - tagsList.add(getJavaBannerPattern((NbtMap) patternTag)); + for (NbtMap patternTag : patterns) { + tagsList.add(getJavaBannerPattern(patternTag)); } return new ListTag("Patterns", tagsList); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/AxolotlBucketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/AxolotlBucketTranslator.java new file mode 100644 index 00000000000..a0fe4910b53 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/AxolotlBucketTranslator.java @@ -0,0 +1,55 @@ +/* + * 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.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.utils.LocaleUtils; + +@ItemRemapper +public class AxolotlBucketTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + // Bedrock Edition displays the properties of the axolotl. Java does not. + // To work around this, set the custom name to the Axolotl translation and it's displayed correctly + itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); + itemTag.put(new StringTag("CustomName", LocaleUtils.getLocaleString("entity.minecraft.axolotl", session.getLocale()))); + // Boilerplate required so the nametag does not appear as "Bucket of " + itemTag.put(new StringTag("ColorID", "")); + itemTag.put(new StringTag("BodyID", "")); + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return itemEntry.getJavaIdentifier().equals("minecraft:axolotl_bucket"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index efb50a7672e..c097dd5443b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -25,18 +25,13 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.utils.ItemUtils; import java.util.ArrayList; import java.util.List; @@ -46,27 +41,27 @@ public class BasicItemTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { - return; + Tag damage = itemTag.get("Damage"); + if (damage instanceof IntTag) { + int originalDurability = ((IntTag) damage).getValue(); + int durability = ItemUtils.getCorrectBedrockDurability(itemEntry.getJavaId(), originalDurability); + if (durability != originalDurability) { + // Fix damage tag inconsistencies + itemTag.put(new IntTag("Damage", durability)); + } } + CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("Name")) { - StringTag nameTag = displayTag.get("Name"); - try { - displayTag.put(new StringTag("Name", toBedrockMessage(nameTag))); - } catch (Exception ex) { - } + if (displayTag == null) { + return; } - if (displayTag.contains("Lore")) { - ListTag loreTag = displayTag.get("Lore"); + ListTag loreTag = displayTag.get("Lore"); + if (loreTag != null) { List lore = new ArrayList<>(); for (Tag tag : loreTag.getValue()) { - if (!(tag instanceof StringTag)) return; - try { - lore.add(new StringTag("", toBedrockMessage((StringTag) tag))); - } catch (Exception ex) { - } + if (!(tag instanceof StringTag)) continue; + lore.add(new StringTag("", MessageTranslator.convertMessageLenient(((StringTag) tag).getValue(), session.getLocale()))); } displayTag.put(new ListTag("Lore", lore)); } @@ -74,54 +69,24 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemE @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { return; } - CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("Name")) { StringTag nameTag = displayTag.get("Name"); - displayTag.put(new StringTag("Name", toJavaMessage(nameTag))); + displayTag.put(new StringTag("Name", MessageTranslator.convertToJavaMessage(nameTag.getValue()))); } if (displayTag.contains("Lore")) { ListTag loreTag = displayTag.get("Lore"); List lore = new ArrayList<>(); for (Tag tag : loreTag.getValue()) { - if (!(tag instanceof StringTag)) return; - lore.add(new StringTag("", "§r" + toJavaMessage((StringTag) tag))); + if (!(tag instanceof StringTag)) continue; + lore.add(new StringTag("", MessageTranslator.convertToJavaMessage(((StringTag) tag).getValue()))); } displayTag.put(new ListTag("Lore", lore)); } } - - private String toJavaMessage(StringTag tag) { - String message = tag.getValue(); - if (message == null) return null; - if (message.startsWith("§r")) { - message = message.replaceFirst("§r", ""); - } - Component component = Component.text(message); - return GsonComponentSerializer.gson().serialize(component); - } - - private String toBedrockMessage(StringTag tag) { - String message = tag.getValue(); - if (message == null) return null; - TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message); - String legacy = LegacyComponentSerializer.legacySection().serialize(component); - if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) { - return "§r" + legacy; - } - return legacy; - } - - private boolean hasFormatting(Component component) { - if (component.hasStyling()) return true; - for (Component child : component.children()) { - if (hasFormatting(child)) { - return true; - } - } - return false; - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 5ddaa997529..197e119fc0b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -28,10 +28,7 @@ import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.*; @ItemRemapper public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { @@ -54,11 +51,13 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemE boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); - if (itemData.contains("tag")) { - // Only the display name is what we have interest in, so just translate that if relevant - CompoundTag displayTag = itemData.get("tag"); - ItemTranslator.translateDisplayProperties(session, displayTag); - boxItemTag.put(displayTag); + // Only the display name is what we have interest in, so just translate that if relevant + CompoundTag displayTag = itemData.get("tag"); + if (displayTag == null && boxItemEntry instanceof TranslatableItemEntry) { + displayTag = new CompoundTag("tag"); + } + if (displayTag != null) { + boxItemTag.put(ItemTranslator.translateDisplayProperties(session, displayTag, boxItemEntry, '7')); } itemsList.add(boxItemTag); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java index 17a3b3792e9..aa22ae46579 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java @@ -36,10 +36,10 @@ */ @Translator(packet = ServerAdvancementTabPacket.class) public class JavaAdvancementsTabTranslator extends PacketTranslator { - @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId()); - session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID); + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + advancementsCache.setCurrentAdvancementCategoryId(packet.getTabId()); + advancementsCache.buildAndShowListForm(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java index 45e65f4751c..0dd7cb95037 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java @@ -55,7 +55,6 @@ public void translate(ServerBossBarPacket packet, GeyserSession session) { case UPDATE_STYLE: case UPDATE_FLAGS: //todo - return; } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java index 942bc7fdcbc..e8e6ae6b7ad 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java @@ -249,7 +249,7 @@ private static Object mapCommandType(CommandParser parser) { return ItemRegistry.ITEM_NAMES; case ITEM_ENCHANTMENT: - return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums + return Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; case ENTITY_SUMMON: return EntityType.ALL_JAVA_IDENTIFIERS; diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java similarity index 66% rename from common/src/main/java/org/geysermc/common/window/component/InputComponent.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java index 9dfc249a200..368693bf2c3 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java @@ -23,30 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.connector.network.translators.java; -import lombok.Getter; -import lombok.Setter; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareTagsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; -public class InputComponent extends FormComponent { +@Translator(packet = ServerDeclareTagsPacket.class) +public class JavaDeclareTagsTranslator extends PacketTranslator { - @Getter - @Setter - private String text; - - @Getter - @Setter - private String placeholder; - - @Getter - @Setter - private String defaultText; - - public InputComponent(String text, String placeholder, String defaultText) { - super("input"); - - this.text = text; - this.placeholder = placeholder; - this.defaultText = defaultText; + @Override + public void translate(ServerDeclareTagsPacket packet, GeyserSession session) { + session.getTagCache().loadPacket(packet); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 33e46c95681..53ebd92ff71 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -34,10 +34,12 @@ import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.PluginMessageUtils; @@ -46,11 +48,15 @@ @Translator(packet = ServerJoinGamePacket.class) public class JavaJoinGameTranslator extends PacketTranslator { + private static final List SKIN_PART_VALUES = Arrays.asList(SkinPart.values()); @Override public void translate(ServerJoinGamePacket packet, GeyserSession session) { PlayerEntity entity = session.getPlayerEntity(); entity.setEntityId(packet.getEntityId()); + + ChunkUtils.applyDimensionHeight(session, packet.getDimension()); + // If the player is already initialized and a join game packet is sent, they // are swapping servers String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); @@ -62,6 +68,8 @@ public void translate(ServerJoinGamePacket packet, GeyserSession session) { } session.setWorldName(packet.getWorldName()); + session.getTagCache().clear(); + AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId()); bedrockPacket.setPlayerPermission(PlayerPermission.MEMBER); @@ -92,12 +100,16 @@ public void translate(ServerJoinGamePacket packet, GeyserSession session) { // We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc String locale = session.getLocale(); - List skinParts = Arrays.asList(SkinPart.values()); - ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); + ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false); session.sendDownstreamPacket(clientSettingsPacket); session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); + // register the plugin messaging channels used in Floodgate + if (session.getConnector().getDefaultAuthType() == AuthType.FLOODGATE) { + session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:register", PluginMessageUtils.getFloodgateRegisterData())); + } + if (!newDimension.equals(session.getDimension())) { DimensionUtils.switchDimension(session, newDimension); } diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java similarity index 61% rename from common/src/main/java/org/geysermc/common/window/FormWindow.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java index efc06fe80f2..c324a81c414 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPingPacket.java @@ -23,37 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window; +package org.geysermc.connector.network.translators.java; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.FormResponse; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPongPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPingPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; -public abstract class FormWindow { +// Why does this packet exist? Whatever, we better implement it +@Translator(packet = ServerPingPacket.class) +public class JavaPingPacket extends PacketTranslator { - @Getter - private final String type; - - @Getter - protected FormResponse response; - - @Getter - @Setter - protected boolean closed; - - public FormWindow(String type) { - this.type = type; - } - - // Lombok won't work here, so we need to make our own method - public void setResponse(FormResponse response) { - this.response = response; + @Override + public void translate(ServerPingPacket packet, GeyserSession session) { + session.sendDownstreamPacket(new ClientPongPacket(packet.getId())); } - - @JsonIgnore - public abstract String getJSONData(); - - public abstract void setResponse(String response); - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java new file mode 100644 index 00000000000..cb80a68a431 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import com.google.common.base.Charsets; +import org.geysermc.connector.common.AuthType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.Forms; +import org.geysermc.cumulus.util.FormType; + +import java.nio.charset.StandardCharsets; + +@Translator(packet = ServerPluginMessagePacket.class) +public class JavaPluginMessageTranslator extends PacketTranslator { + @Override + public void translate(ServerPluginMessagePacket packet, GeyserSession session) { + // The only plugin messages it has to listen for are Floodgate plugin messages + if (session.getConnector().getDefaultAuthType() != AuthType.FLOODGATE) { + return; + } + + String channel = packet.getChannel(); + + if (channel.equals("floodgate:form")) { + byte[] data = packet.getData(); + + // receive: first byte is form type, second and third are the id, remaining is the form data + // respond: first and second byte id, remaining is form response data + + FormType type = FormType.getByOrdinal(data[0]); + if (type == null) { + throw new NullPointerException( + "Got type " + data[0] + " which isn't a valid form type!"); + } + + String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8); + + Form form = Forms.fromJson(dataString, type); + form.setResponseHandler(response -> { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData)); + }); + session.sendForm(form); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index 7c8cd0583ae..908292d360c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -36,6 +36,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; @Translator(packet = ServerRespawnPacket.class) @@ -78,6 +79,8 @@ public void translate(ServerRespawnPacket packet, GeyserSession session) { session.setThunder(false); } + ChunkUtils.applyDimensionHeight(session, packet.getDimension()); + String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) { if (!packet.getWorldName().equals(session.getWorldName()) && session.getDimension().equals(newDimension)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java index 9bf810e2163..24780804124 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -40,7 +40,7 @@ public void translate(ServerStatisticsPacket packet, GeyserSession session) { if (session.isWaitingForStatistics()) { session.setWaitingForStatistics(false); - session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + StatisticsUtils.buildAndSendStatisticsMenu(session); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java index 735a5ea4713..255c4f46627 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java @@ -25,22 +25,27 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.AnimateEntityPacket; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; -import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import org.geysermc.connector.utils.DimensionUtils; @Translator(packet = ServerEntityAnimationPacket.class) public class JavaEntityAnimationTranslator extends PacketTranslator { @Override public void translate(ServerEntityAnimationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; @@ -51,11 +56,30 @@ public void translate(ServerEntityAnimationPacket packet, GeyserSession session) case SWING_ARM: animatePacket.setAction(AnimatePacket.Action.SWING_ARM); break; + case EAT_FOOD: // ACTUALLY SWING OFF HAND + // Use the OptionalPack to trigger the animation + AnimateEntityPacket offHandPacket = new AnimateEntityPacket(); + offHandPacket.setAnimation("animation.player.attack.rotations.offhand"); + offHandPacket.setNextState("default"); + offHandPacket.setBlendOutTime(0.0f); + offHandPacket.setStopExpression("query.any_animation_finished"); + offHandPacket.setController("__runtime_controller"); + offHandPacket.getRuntimeEntityIds().add(entity.getGeyserId()); + + session.sendUpstreamPacket(offHandPacket); + return; case CRITICAL_HIT: animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT); break; case ENCHANTMENT_CRITICAL_HIT: - animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); + animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); // Unsure if this does anything + // Spawn custom particle + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier("geyseropt:enchanted_hit_multiple"); + stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + stringPacket.setPosition(Vector3f.ZERO); + stringPacket.setUniqueEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(stringPacket); break; case LEAVE_BED: animatePacket.setAction(AnimatePacket.Action.WAKE_UP); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 5d6706fefb2..66d3e38804f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -26,20 +26,16 @@ package org.geysermc.connector.network.translators.java.entity; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntitySetPassengersPacket; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; -import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.living.ArmorStandEntity; -import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.EntityUtils; import java.util.Arrays; @@ -65,9 +61,7 @@ public void translate(ServerEntitySetPassengersPacket packet, GeyserSession sess passenger = session.getPlayerEntity(); session.setRidingVehicleEntity(entity); // We need to confirm teleports before entering a vehicle, or else we will likely exit right out - if (session.getConnector().getConfig().isCacheChunks()) { - session.confirmTeleport(passenger.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0).toDouble()); - } + session.confirmTeleport(passenger.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0).toDouble()); } // Passenger hasn't loaded in (likely since we're waiting for a skin response) // and entity link needs to be set later @@ -88,13 +82,8 @@ public void translate(ServerEntitySetPassengersPacket packet, GeyserSession sess if (entity.getEntityType() == EntityType.BOAT) { passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 1); passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f); - // Can be removed once 1.16.200 to 1.16.201 support is dropped - if (session.getUpstream().getSession().getPacketCodec().getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion()) { - passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 1f); - passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, -90f); - } else { - passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, -90f); - } + passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 1f); + passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, -90f); } else { passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); @@ -123,13 +112,11 @@ public void translate(ServerEntitySetPassengersPacket packet, GeyserSession sess passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f); - if (session.getUpstream().getSession().getPacketCodec().getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion()) { - passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); - } + passenger.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); - this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); + EntityUtils.updateMountOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); } else { - this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); + EntityUtils.updateMountOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); } // Force an update to the passenger metadata @@ -147,144 +134,4 @@ public void translate(ServerEntitySetPassengersPacket packet, GeyserSession sess break; } } - - private float getMountedHeightOffset(Entity mount) { - float height = mount.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); - float mountedHeightOffset = height * 0.75f; - switch (mount.getEntityType()) { - case CHICKEN: - case SPIDER: - mountedHeightOffset = height * 0.5f; - break; - case DONKEY: - case MULE: - mountedHeightOffset -= 0.25f; - break; - case LLAMA: - mountedHeightOffset = height * 0.67f; - break; - case MINECART: - case MINECART_HOPPER: - case MINECART_TNT: - case MINECART_CHEST: - case MINECART_FURNACE: - case MINECART_SPAWNER: - case MINECART_COMMAND_BLOCK: - mountedHeightOffset = 0; - break; - case BOAT: - mountedHeightOffset = -0.1f; - break; - case HOGLIN: - case ZOGLIN: - boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); - mountedHeightOffset = height - (isBaby ? 0.2f : 0.15f); - break; - case PIGLIN: - mountedHeightOffset = height * 0.92f; - break; - case RAVAGER: - mountedHeightOffset = 2.1f; - break; - case SKELETON_HORSE: - mountedHeightOffset -= 0.1875f; - break; - case STRIDER: - mountedHeightOffset = height - 0.19f; - break; - } - return mountedHeightOffset; - } - - private float getHeightOffset(Entity passenger) { - boolean isBaby; - switch (passenger.getEntityType()) { - case SKELETON: - case STRAY: - case WITHER_SKELETON: - return -0.6f; - case ARMOR_STAND: - if (((ArmorStandEntity) passenger).isMarker()) { - return 0.0f; - } else { - return 0.1f; - } - case ENDERMITE: - case SILVERFISH: - return 0.1f; - case PIGLIN: - case PIGLIN_BRUTE: - case ZOMBIFIED_PIGLIN: - isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); - return isBaby ? -0.05f : -0.45f; - case ZOMBIE: - isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); - return isBaby ? 0.0f : -0.45f; - case EVOKER: - case ILLUSIONER: - case PILLAGER: - case RAVAGER: - case VINDICATOR: - case WITCH: - return -0.45f; - case PLAYER: - return -0.35f; - } - if (passenger instanceof AnimalEntity) { - return 0.14f; - } - return 0f; - } - - private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { - passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); - if (riding) { - // Without the Y offset, Bedrock players will find themselves in the floor when mounting - float mountedHeightOffset = getMountedHeightOffset(mount); - float heightOffset = getHeightOffset(passenger); - - float xOffset = 0; - float yOffset = mountedHeightOffset + heightOffset; - float zOffset = 0; - switch (mount.getEntityType()) { - case BOAT: - // Without the X offset, more than one entity on a boat is stacked on top of each other - if (rider && moreThanOneEntity) { - xOffset = 0.2f; - } else if (moreThanOneEntity) { - xOffset = -0.6f; - } - break; - case CHICKEN: - zOffset = -0.1f; - break; - case LLAMA: - zOffset = -0.3f; - break; - } - /* - * Bedrock Differences - * Zoglin & Hoglin seem to be taller in Bedrock edition - * Horses are tinier - * Players, Minecarts, and Boats have different origins - */ - if (passenger.getEntityType() == EntityType.PLAYER && mount.getEntityType() != EntityType.PLAYER) { - yOffset += EntityType.PLAYER.getOffset(); - } - switch (mount.getEntityType()) { - case MINECART: - case MINECART_HOPPER: - case MINECART_TNT: - case MINECART_CHEST: - case MINECART_FURNACE: - case MINECART_SPAWNER: - case MINECART_COMMAND_BLOCK: - case BOAT: - yOffset -= mount.getEntityType().getHeight() * 0.5f; - } - Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); - passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); - } - passenger.updateBedrockMetadata(session); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index 59ea29925c7..f444d3ac186 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -30,12 +30,14 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -47,9 +49,11 @@ public class JavaEntityStatusTranslator extends PacketTranslator { +@Translator(packet = ServerRemoveEntityPacket.class) +public class JavaRemoveEntityTranslator extends PacketTranslator { @Override - public void translate(ServerEntityDestroyPacket packet, GeyserSession session) { - for (int entityId : packet.getEntityIds()) { - Entity entity = session.getEntityCache().getEntityByJavaId(entityId); + public void translate(ServerRemoveEntityPacket packet, GeyserSession session) { + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (entity != null) { - session.getEntityCache().removeEntity(entity, false); - } + if (entity != null) { + session.getEntityCache().removeEntity(entity, false); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java index ace9cb9345b..020c554ffb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java @@ -25,23 +25,15 @@ package org.geysermc.connector.network.translators.java.entity.player; -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.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.connector.inventory.GeyserItemStack; -import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerPlayerActionAckPacket.class) @@ -58,45 +50,5 @@ public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID); session.sendUpstreamPacket(stopBreak); } - if (!session.getConnector().getConfig().isCacheChunks()) { - LevelEventPacket levelEvent = new LevelEventPacket(); - switch (packet.getAction()) { - case START_DIGGING: - if (session.getGameMode() == GameMode.CREATIVE) { - break; - } - double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState()); - levelEvent.setType(LevelEventType.BLOCK_START_BREAK); - levelEvent.setPosition(Vector3f.from( - packet.getPosition().getX(), - packet.getPosition().getY(), - packet.getPosition().getZ() - )); - PlayerInventory inventory = session.getPlayerInventory(); - GeyserItemStack item = inventory.getItemInHand(); - ItemEntry itemEntry = null; - CompoundTag nbtData = new CompoundTag(""); - if (item != null) { - itemEntry = item.getItemEntry(); - nbtData = item.getNbt(); - } - double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), itemEntry, nbtData, session) * 20); - levelEvent.setData((int) (65535 / breakTime)); - session.setBreakingBlock(packet.getNewState()); - session.sendUpstreamPacket(levelEvent); - break; - case CANCEL_DIGGING: - levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK); - levelEvent.setPosition(Vector3f.from( - packet.getPosition().getX(), - packet.getPosition().getY(), - packet.getPosition().getZ() - )); - levelEvent.setData(0); - session.setBreakingBlock(0); - session.sendUpstreamPacket(levelEvent); - break; - } - } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index b379443e3d5..3865ef64b20 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -29,9 +29,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; 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.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,6 +43,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.connector.utils.EntityUtils; import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerPlayerPositionRotationPacket.class) @@ -87,19 +92,20 @@ public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession s session.setSpawned(true); - // Ignore certain move correction packets for smoother movement - // These are never relative - // When chunk caching is enabled this isn't needed as we shouldn't get these - if (!session.getConnector().getConfig().isCacheChunks() && packet.getRelative().isEmpty()) { - double xDis = Math.abs(entity.getPosition().getX() - packet.getX()); - double yDis = entity.getPosition().getY() - packet.getY(); - double zDis = Math.abs(entity.getPosition().getZ() - packet.getZ()); - if (!(xDis > 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5)) { - // Fake confirm the teleport but don't send it to the client - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.sendDownstreamPacket(teleportConfirmPacket); - return; - } + if (packet.isDismountVehicle() && session.getRidingVehicleEntity() != null) { + Entity vehicle = session.getRidingVehicleEntity(); + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(vehicle.getGeyserId(), entity.getGeyserId(), EntityLinkData.Type.REMOVE, false, false)); + session.sendUpstreamPacket(linkPacket); + vehicle.getPassengers().remove(entity.getEntityId()); + entity.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); + entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); + entity.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f); + entity.getMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); + session.setRidingVehicleEntity(null); + entity.updateBedrockMetadata(session); + + EntityUtils.updateMountOffset(entity, vehicle, session, false, false, entity.getPassengers().size() > 1); } // If coordinates are relative, then add to the existing coordinate diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java index b6a41fa4254..7bce03d72b4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java @@ -64,7 +64,7 @@ public void translate(ServerSpawnEntityPacket packet, GeyserSession session) { if (packet.getType() == EntityType.FALLING_BLOCK) { entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId()); - } else if (packet.getType() == EntityType.ITEM_FRAME) { + } else if (packet.getType() == EntityType.ITEM_FRAME || packet.getType() == EntityType.GLOW_ITEM_FRAME) { // Item frames need the hanging direction entity = new ItemFrameEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), type, position, motion, rotation, (HangingDirection) packet.getData()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java new file mode 100644 index 00000000000..e12276b1f9b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaClearTitlesTranslator.java @@ -0,0 +1,45 @@ +/* + * 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.connector.network.translators.java.title; + +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerClearTitlesPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerClearTitlesPacket.class) +public class JavaClearTitlesTranslator extends PacketTranslator { + + @Override + public void translate(ServerClearTitlesPacket packet, GeyserSession session) { + SetTitlePacket titlePacket = new SetTitlePacket(); + // TODO handle packet.isResetTimes() + titlePacket.setType(SetTitlePacket.Type.CLEAR); + titlePacket.setText(""); + session.sendUpstreamPacket(titlePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java index ffda57826a3..a659a3fb2ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetActionBarTextTranslator.java @@ -23,58 +23,30 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.connector.network.translators.java.title; +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetActionBarTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.chat.MessageTranslator; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerTitlePacket; -import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; - -@Translator(packet = ServerTitlePacket.class) -public class JavaTitleTranslator extends PacketTranslator { +@Translator(packet = ServerSetActionBarTextPacket.class) +public class JavaSetActionBarTextTranslator extends PacketTranslator { @Override - public void translate(ServerTitlePacket packet, GeyserSession session) { - SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getLocale(); - + public void translate(ServerSetActionBarTextPacket packet, GeyserSession session) { String text; - if (packet.getTitle() == null) { + if (packet.getText() == null) { //TODO 1.17 can this happen? text = " "; } else { - text = MessageTranslator.convertMessage(packet.getTitle(), locale); - } - - switch (packet.getAction()) { - case TITLE: - titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(text); - break; - case SUBTITLE: - titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(text); - break; - case CLEAR: - case RESET: - titlePacket.setType(SetTitlePacket.Type.CLEAR); - titlePacket.setText(""); - break; - case ACTION_BAR: - titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(text); - break; - case TIMES: - titlePacket.setType(SetTitlePacket.Type.TIMES); - titlePacket.setFadeInTime(packet.getFadeIn()); - titlePacket.setFadeOutTime(packet.getFadeOut()); - titlePacket.setStayTime(packet.getStay()); - titlePacket.setText(""); - break; + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); } + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); + titlePacket.setText(text); session.sendUpstreamPacket(titlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java new file mode 100644 index 00000000000..d0f97cc3dce --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetSubtitleTextTranslator.java @@ -0,0 +1,52 @@ +/* + * 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.connector.network.translators.java.title; + +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetSubtitleTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; + +@Translator(packet = ServerSetSubtitleTextPacket.class) +public class JavaSetSubtitleTextTranslator extends PacketTranslator { + + @Override + public void translate(ServerSetSubtitleTextPacket packet, GeyserSession session) { + String text; + if (packet.getText() == null) { //TODO 1.17 can this happen? + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + } + + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.SUBTITLE); + titlePacket.setText(text); + session.sendUpstreamPacket(titlePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java new file mode 100644 index 00000000000..7cf06a4f7ed --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitleTextTranslator.java @@ -0,0 +1,52 @@ +/* + * 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.connector.network.translators.java.title; + +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetTitleTextPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; + +@Translator(packet = ServerSetTitleTextPacket.class) +public class JavaSetTitleTextTranslator extends PacketTranslator { + + @Override + public void translate(ServerSetTitleTextPacket packet, GeyserSession session) { + String text; + if (packet.getText() == null) { //TODO 1.17 can this happen? + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getText(), session.getLocale()); + } + + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TITLE); + titlePacket.setText(text); + session.sendUpstreamPacket(titlePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java index 3b55733bf80..9e2c13eb532 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/title/JavaSetTitlesAnimationTranslator.java @@ -23,25 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.window; +package org.geysermc.connector.network.translators.java.title; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; -import org.geysermc.connector.inventory.Inventory; +import com.github.steveice10.mc.protocol.packet.ingame.server.title.ServerSetTitlesAnimationPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -@Translator(packet = ServerConfirmTransactionPacket.class) -public class JavaConfirmTransactionTranslator extends PacketTranslator { +@Translator(packet = ServerSetTitlesAnimationPacket.class) +public class JavaSetTitlesAnimationTranslator extends PacketTranslator { @Override - public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) { - session.addInventoryTask(() -> { - if (!packet.isAccepted()) { - ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); - session.sendDownstreamPacket(confirmPacket); - } - }); + public void translate(ServerSetTitlesAnimationPacket packet, GeyserSession session) { + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setType(SetTitlePacket.Type.TIMES); + titlePacket.setText(""); + titlePacket.setFadeInTime(packet.getFadeIn()); + titlePacket.setFadeOutTime(packet.getFadeOut()); + titlePacket.setStayTime(packet.getStay()); + session.sendUpstreamPacket(titlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java index 559d1a922e6..27f84219107 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java @@ -43,7 +43,7 @@ public class JavaBlockBreakAnimTranslator extends PacketTranslator { - /** - * Determines if we should process non-full chunks - */ - private final boolean cacheChunks; - - public JavaChunkDataTranslator() { - cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - } @Override public void translate(ServerChunkDataPacket packet, GeyserSession session) { @@ -60,23 +52,15 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt()); } - if (packet.getColumn().getBiomeData() == null && !cacheChunks) { - // Non-full chunk without chunk caching - session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off."); - return; - } - - // Merge received column with cache on network thread - Column mergedColumn = session.getChunkCache().addToCache(packet.getColumn()); - if (mergedColumn == null) { // There were no changes?!? - return; - } + session.getChunkCache().addToCache(packet.getColumn()); + Column column = packet.getColumn(); - boolean isNonFullChunk = packet.getColumn().getBiomeData() == null; + // Ensure that, if the player is using lower world heights, the position is not offset + int yOffset = session.getChunkCache().getChunkMinY(); GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { try { - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, column, yOffset); ChunkSection[] sections = chunkData.getSections(); // Find highest section @@ -106,7 +90,7 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { (section != null ? section : session.getBlockTranslator().getEmptyChunkSection()).writeToNetwork(byteBuf); } - byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes + byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(column.getBiomeData())); // Biomes - 256 bytes byteBuf.writeByte(0); // Border blocks - Edu edition only VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now @@ -125,8 +109,8 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); levelChunkPacket.setSubChunksLength(sectionCount); levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setChunkX(mergedColumn.getX()); - levelChunkPacket.setChunkZ(mergedColumn.getZ()); + levelChunkPacket.setChunkX(column.getX()); + levelChunkPacket.setChunkZ(column.getZ()); levelChunkPacket.setData(payload); session.sendUpstreamPacket(levelChunkPacket); } catch (Exception ex) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java index 7f15213ca03..905345ceea8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java @@ -39,7 +39,6 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.effect.Effect; import org.geysermc.connector.network.translators.effect.EffectRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.LocaleUtils; import java.util.Collections; @@ -229,6 +228,7 @@ public void translate(ServerPlayEffectPacket packet, GeyserSession session) { effectPacket.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java break; } + case BONEMEAL_GROW_WITH_SOUND: // Note that there is no particle without sound in Bedrock. If you wanted to implement the sound, send a PlaySoundPacket with "item.bone_meal.use" and volume and pitch at 1.0F case BONEMEAL_GROW: { effectPacket.setType(LevelEventType.PARTICLE_CROP_GROWTH); @@ -275,6 +275,27 @@ public void translate(ServerPlayEffectPacket packet, GeyserSession session) { session.sendUpstreamPacket(soundEventPacket); break; } + case DRIPSTONE_DRIP: { + effectPacket.setType(LevelEventType.PARTICLE_DRIPSTONE_DRIP); + break; + } + case ELECTRIC_SPARK: { + // Matches with a Bedrock server but doesn't seem to match up with Java + effectPacket.setType(LevelEventType.PARTICLE_ELECTRIC_SPARK); + break; + } + case WAX_ON: { + effectPacket.setType(LevelEventType.PARTICLE_WAX_ON); + break; + } + case WAX_OFF: { + effectPacket.setType(LevelEventType.PARTICLE_WAX_OFF); + break; + } + case SCRAPE: { + effectPacket.setType(LevelEventType.PARTICLE_SCRAPE); + break; + } default: { GeyserConnector.getInstance().getLogger().debug("Unhandled particle effect: " + particleEffect.name()); return; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index 6115cb57ead..77f00bb1e5c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -26,12 +26,10 @@ package org.geysermc.connector.network.translators.java.world; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.world.particle.BlockParticleData; -import com.github.steveice10.mc.protocol.data.game.world.particle.DustParticleData; -import com.github.steveice10.mc.protocol.data.game.world.particle.FallingDustParticleData; -import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData; +import com.github.steveice10.mc.protocol.data.game.world.particle.*; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnParticlePacket; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; @@ -43,65 +41,118 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.DimensionUtils; +import java.util.Random; +import java.util.function.Function; + @Translator(packet = ServerSpawnParticlePacket.class) public class JavaSpawnParticleTranslator extends PacketTranslator { + private final Random random = new Random(); @Override public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { - LevelEventPacket particle = new LevelEventPacket(); - switch (packet.getParticle().getType()) { - case BLOCK: - particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); - session.sendUpstreamPacket(particle); - break; - case FALLING_DUST: - //In fact, FallingDustParticle should have data like DustParticle, - //but in MCProtocol, its data is BlockState(1). - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case ITEM: - ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); + Function particleCreateFunction = createParticle(session, packet.getParticle()); + if (particleCreateFunction != null) { + if (packet.getAmount() == 0) { + // 0 means don't apply the offset + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + session.sendUpstreamPacket(particleCreateFunction.apply(position)); + } else { + for (int i = 0; i < packet.getAmount(); i++) { + double offsetX = this.random.nextGaussian() * (double) packet.getOffsetX(); + double offsetY = this.random.nextGaussian() * (double) packet.getOffsetY(); + double offsetZ = this.random.nextGaussian() * (double) packet.getOffsetZ(); + Vector3f position = Vector3f.from(packet.getX() + offsetX, packet.getY() + offsetY, packet.getZ() + offsetZ); + + session.sendUpstreamPacket(particleCreateFunction.apply(position)); + } + } + } else { + // Null is only returned when no particle of this type is found + session.getConnector().getLogger().debug("Unhandled particle packet: " + packet); + } + } + + /** + * @param session the Bedrock client session. + * @param particle the Java particle to translate to a Bedrock equivalent. + * @return a function to create a packet with a specified particle, in the event we need to spawn multiple particles + * with different offsets. + */ + private Function createParticle(GeyserSession session, Particle particle) { + switch (particle.getType()) { + case BLOCK: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_CRACK_BLOCK); + packet.setPosition(position); + packet.setData(blockState); + return packet; + }; + } + case FALLING_DUST: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + // In fact, FallingDustParticle should have data like DustParticle, + // but in MCProtocol, its data is BlockState(1). + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(blockState); + packet.setPosition(position); + return packet; + }; + } + case ITEM: { + ItemStack javaItem = ((ItemParticleData) particle.getData()).getItemStack(); ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); - int id = bedrockItem.getId(); - int damage = bedrockItem.getDamage(); - particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); - particle.setData(id << 16 | damage); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; + int data = bedrockItem.getId() << 16 | bedrockItem.getDamage(); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_ITEM_BREAK); + packet.setData(data); + packet.setPosition(position); + return packet; + }; + } case DUST: - DustParticleData data = (DustParticleData)packet.getParticle().getData(); - int r = (int) (data.getRed()*255); - int g = (int) (data.getGreen()*255); - int b = (int) (data.getBlue()*255); - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; + case DUST_COLOR_TRANSITION: { //TODO + DustParticleData data = (DustParticleData) particle.getData(); + int r = (int) (data.getRed() * 255); + int g = (int) (data.getGreen() * 255); + int b = (int) (data.getBlue() * 255); + int rgbData = ((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(rgbData); + packet.setPosition(position); + return packet; + }; + } default: - LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(packet.getParticle().getType()); + LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(particle.getType()); if (typeParticle != null) { - particle.setType(typeParticle); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(typeParticle); + packet.setPosition(position); + return packet; + }; } else { - String stringParticle = EffectRegistry.getParticleString(packet.getParticle().getType()); + String stringParticle = EffectRegistry.getParticleString(particle.getType()); if (stringParticle != null) { - SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); - stringPacket.setIdentifier(stringParticle); - stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); - stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(stringPacket); + int dimensionId = DimensionUtils.javaToBedrock(session.getDimension()); + return (position) -> { + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier(stringParticle); + stringPacket.setDimensionId(dimensionId); + stringPacket.setPosition(position); + return stringPacket; + }; + } else { + return null; } } - break; } } - } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java index 014d2d73114..0f2cb8814db 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java @@ -29,41 +29,38 @@ import com.github.steveice10.mc.protocol.data.game.world.block.UpdatedTileType; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTileEntityPacket; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; +import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; import org.geysermc.connector.utils.BlockEntityUtils; -import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerUpdateTileEntityPacket.class) public class JavaUpdateTileEntityTranslator extends PacketTranslator { - private final boolean cacheChunks; - - public JavaUpdateTileEntityTranslator() { - cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - } @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); - if (packet.getNbt().isEmpty()) { // Fixes errors in CubeCraft sending empty NBT - BlockEntityUtils.updateBlockEntity(session, null, packet.getPosition()); + if (packet.getNbt().isEmpty()) { // Fixes errors in servers sending empty NBT + BlockEntityUtils.updateBlockEntity(session, NbtMap.EMPTY, packet.getPosition()); return; } BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id); // The Java block state is used in BlockEntityTranslator.translateTag() to make up for some inconsistencies // between Java block states and Bedrock block entity data - int blockState = cacheChunks ? - // Cache chunks is enabled; use chunk cache - session.getConnector().getWorldManager().getBlockAt(session, packet.getPosition()) : - // Cache chunks is not enabled; use block entity cache - ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(packet.getPosition()); + int blockState; + if (translator instanceof RequiresBlockState) { + blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getPosition()); + } else { + blockState = BlockTranslator.JAVA_AIR_ID; + } BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), blockState), packet.getPosition()); // Check for custom skulls. if (SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS && packet.getNbt().contains("SkullOwner")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java index cb15022d0d7..d304e110820 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java @@ -32,7 +32,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateViewPositionPacket; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; @Translator(packet = ServerUpdateViewPositionPacket.class) public class JavaUpdateViewPositionTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java index 4ae0d7c656f..013c503d084 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java @@ -40,7 +40,6 @@ public void translate(ServerVehicleMovePacket packet, GeyserSession session) { Entity entity = session.getRidingVehicleEntity(); if (entity == null) return; - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, false); - + entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java index 2172fd9e6fa..ea9951b8346 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java @@ -25,11 +25,9 @@ package org.geysermc.connector.network.translators.sound; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; import java.util.Map; @@ -47,6 +45,9 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { if (!(interactionEntry.getValue() instanceof BlockSoundInteractionHandler)) { continue; @@ -66,7 +67,9 @@ static void handleBlockInteraction(GeyserSession session, Vector3f position, Str if (itemInHand.isEmpty()) { continue; } - String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); + if (handIdentifier == null) { + handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); + } boolean contains = false; for (String itemIdentifier : interactionEntry.getKey().items()) { if (handIdentifier.contains(itemIdentifier)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java index 49d4f077736..2c4f38e25a9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java @@ -25,12 +25,10 @@ package org.geysermc.connector.network.translators.sound; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; import java.util.Map; @@ -48,6 +46,9 @@ public interface EntitySoundInteractionHandler extends SoundInteractionHandler> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { if (!(interactionEntry.getValue() instanceof EntitySoundInteractionHandler)) { continue; @@ -67,7 +68,10 @@ static void handleEntityInteraction(GeyserSession session, Vector3f position, En if (itemInHand.isEmpty()) { continue; } - String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); + if (handIdentifier == null) { + // Don't get the identifier unless we need it + handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); + } boolean contains = false; for (String itemIdentifier : interactionEntry.getKey().items()) { if (handIdentifier.contains(itemIdentifier)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java index 4af1b820cd4..f12e91db2fa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java @@ -59,7 +59,7 @@ * Leave empty to ignore. * * Only applies to interaction handlers that are an - * instance of {@link BlockSoundInteractionHandler}. + * instance of {@link EntitySoundInteractionHandler}. * * @return the value the item in the player's hand must contain */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java index 8ebca00ec6f..90599eb198f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java @@ -70,7 +70,7 @@ public static void init() { brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null, brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1, brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null, - brMap.has("level_event") && brMap.get("level_event").isBoolean() ? brMap.get("level_event").asBoolean() : false + brMap.has("level_event") && brMap.get("level_event").isBoolean() && brMap.get("level_event").asBoolean() ) ); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java index afb9ccc6acc..227b295219c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java @@ -29,7 +29,6 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; import org.geysermc.connector.network.translators.sound.SoundHandler; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractionHandler.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java rename to connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractionHandler.java index ba436374b25..86a68ad30d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractionHandler.java @@ -33,7 +33,7 @@ import org.geysermc.connector.network.translators.sound.SoundHandler; @SoundHandler(blocks = "comparator") -public class ComparatorSoundInteractHandler implements BlockSoundInteractionHandler { +public class ComparatorSoundInteractionHandler implements BlockSoundInteractionHandler { @Override public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java new file mode 100644 index 00000000000..7482ad56576 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java @@ -0,0 +1,58 @@ +/* + * 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.connector.network.translators.sound.entity; + +import com.nukkitx.math.vector.Vector3f; +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.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; +import org.geysermc.connector.entity.living.animal.OcelotEntity; +import org.geysermc.connector.entity.living.animal.tameable.CatEntity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler +public class FeedBabySoundInteractionHandler implements EntitySoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, Entity entity) { + if (entity instanceof AnimalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) { + String handIdentifier = session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier(); + boolean isBaby = entity.getMetadata().getFlags().getFlag(EntityFlag.BABY); + if (isBaby && ((AnimalEntity) entity).canEat(session, handIdentifier.replace("minecraft:", ""), + session.getPlayerInventory().getItemInHand().getItemEntry())) { + // 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/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkEntitySoundInteractionHandler.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java rename to connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkEntitySoundInteractionHandler.java index ffd4580100d..cc9516d73b6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkEntitySoundInteractionHandler.java @@ -27,27 +27,38 @@ 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.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.GoatEntity; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; import org.geysermc.connector.network.translators.sound.SoundHandler; -@SoundHandler(entities = "cow", items = "bucket") -public class MilkCowSoundInteractionHandler implements EntitySoundInteractionHandler { +@SoundHandler(entities = {"cow", "goat"}, items = "bucket") +public class MilkEntitySoundInteractionHandler implements EntitySoundInteractionHandler { @Override public void handleInteraction(GeyserSession session, Vector3f position, Entity value) { if (!session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier().equals("minecraft:bucket")) { return; } + if (value.getMetadata().getFlags().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(SoundEvent.MILK); + levelSoundEventPacket.setSound(milkSound); levelSoundEventPacket.setExtraData(-1); session.sendUpstreamPacket(levelSoundEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 014f3e36698..80bb08b061e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -25,8 +25,6 @@ package org.geysermc.connector.network.translators.world; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; @@ -53,45 +51,12 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return BlockTranslator.JAVA_AIR_ID; } - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - ChunkCache chunkCache = session.getChunkCache(); - Column cachedColumn; - Chunk cachedChunk; - if (chunkCache == null || (cachedColumn = chunkCache.getChunk(x, z)) == null || (cachedChunk = cachedColumn.getChunks()[y]) == null) { - return; - } - - // Copy state IDs from cached chunk to output chunk - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, cachedChunk.get(blockX, blockY, blockZ)); - } - } - } - } - @Override public boolean hasOwnChunkCache() { // This implementation can only fetch data from the session chunk cache return false; } - @Override - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (session.getConnector().getConfig().isCacheChunks()) { - ChunkCache chunkCache = session.getChunkCache(); - if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously - Column column = chunkCache.getChunk(x, z); - if (column != null) { // Column can be null if the server sent a partial chunk update before the first ground-up-continuous one - return column.getBiomeData(); - } - } - } - return new int[1024]; - } - @Override public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { // Without direct server access, we can't get lectern information on-the-fly. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index e97dcec326e..ca945bfb4b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.world; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; @@ -76,17 +75,6 @@ public int getBlockAt(GeyserSession session, Vector3i vector) { */ public abstract int getBlockAt(GeyserSession session, int x, int y, int z); - /** - * Gets all block states in the specified chunk section. - * - * @param session the session - * @param x the chunk's X coordinate - * @param y the chunk's Y coordinate - * @param z the chunk's Z coordinate - * @param section the chunk section to store the block data in - */ - public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section); - /** * Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache. *

@@ -97,16 +85,6 @@ public int getBlockAt(GeyserSession session, Vector3i vector) { */ public abstract boolean hasOwnChunkCache(); - /** - * Gets the Java biome data for the specified chunk. - * - * @param session the session of the player - * @param x the chunk's X coordinate - * @param z the chunk's Z coordinate - * @return the biome data for the specified region with a length of 1024. - */ - public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z); - /** * Sigh.
* diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 1a2624a6f88..f5b56749088 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -38,13 +38,12 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; import org.geysermc.connector.network.translators.world.chunk.EmptyChunkProvider; +import org.geysermc.connector.registry.type.BlockMapping; import org.geysermc.connector.utils.FileUtils; import java.io.DataInputStream; import java.io.InputStream; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.zip.GZIPInputStream; public abstract class BlockTranslator { @@ -74,14 +73,7 @@ public abstract class BlockTranslator { private final Object2IntMap itemFrames = new Object2IntOpenHashMap<>(); private final Map flowerPotBlocks = new HashMap<>(); - public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); - public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); - public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); - - // The index of the collision data in collision.json - public static final Int2IntMap JAVA_RUNTIME_ID_TO_COLLISION_INDEX = new Int2IntOpenHashMap(); - - private static final Int2ObjectMap JAVA_RUNTIME_ID_TO_PICK_ITEM = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap JAVA_RUNTIME_ID_TO_BLOCK_MAPPING = new Int2ObjectOpenHashMap<>(); /** * Java numeric ID to java unique identifier, used for block names in the statistics screen @@ -96,11 +88,8 @@ public abstract class BlockTranslator { private final EmptyChunkProvider emptyChunkProvider; - /** - * A list of all Java runtime wool IDs, for use with block breaking math and shears - */ - public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); - public static final int JAVA_RUNTIME_COBWEB_ID; + public static final int JAVA_COBWEB_BLOCK_ID; + public static final int JAVA_BELL_BLOCK_ID; public static final int JAVA_RUNTIME_FURNACE_ID; public static final int JAVA_RUNTIME_FURNACE_LIT_ID; @@ -127,7 +116,8 @@ public abstract class BlockTranslator { } int javaRuntimeId = -1; - int cobwebRuntimeId = -1; + int bellBlockId = -1; + int cobwebBlockId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; @@ -139,31 +129,35 @@ public abstract class BlockTranslator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); + BlockMapping.BlockMappingBuilder builder = BlockMapping.builder(); // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); if (hardnessNode != null) { - JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue()); - } - - try { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue()); - } catch (Exception e) { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, false); + builder.hardness(hardnessNode.doubleValue()); } - JsonNode toolTypeNode = entry.getValue().get("tool_type"); - if (toolTypeNode != null) { - JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); + JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand"); + if (canBreakWithHandNode != null) { + builder.canBreakWithHand(canBreakWithHandNode.booleanValue()); + } else { + builder.canBreakWithHand(false); } JsonNode collisionIndexNode = entry.getValue().get("collision_index"); if (hardnessNode != null) { - JAVA_RUNTIME_ID_TO_COLLISION_INDEX.put(javaRuntimeId, collisionIndexNode.intValue()); + builder.collisionIndex(collisionIndexNode.intValue()); } JsonNode pickItemNode = entry.getValue().get("pick_item"); if (pickItemNode != null) { - JAVA_RUNTIME_ID_TO_PICK_ITEM.put(javaRuntimeId, pickItemNode.textValue()); + builder.pickItem(pickItemNode.textValue()); + } + + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); + + if (waterlogged) { + WATERLOGGED.add(javaRuntimeId); } JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); @@ -183,11 +177,17 @@ public abstract class BlockTranslator { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } - if (javaId.contains("wool")) { - JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + builder.javaBlockId(uniqueJavaId); + + builder.javaIdentifier(javaId); + + JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.put(javaRuntimeId, builder.build()); + + if (javaId.startsWith("minecraft:bell[")) { + bellBlockId = uniqueJavaId; } else if (javaId.contains("cobweb")) { - cobwebRuntimeId = javaRuntimeId; + cobwebBlockId = uniqueJavaId; } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { @@ -204,10 +204,15 @@ public abstract class BlockTranslator { } } - if (cobwebRuntimeId == -1) { + if (bellBlockId == -1) { + throw new AssertionError("Unable to find bell in palette"); + } + JAVA_BELL_BLOCK_ID = bellBlockId; + + if (cobwebBlockId == -1) { throw new AssertionError("Unable to find cobwebs in palette"); } - JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + JAVA_COBWEB_BLOCK_ID = cobwebBlockId; if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); @@ -229,8 +234,9 @@ public abstract class BlockTranslator { } JAVA_WATER_ID = waterRuntimeId; - BlockTranslator1_16_100.init(); - BlockTranslator1_16_210.init(); + BlockMapping.AIR = JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.get(JAVA_AIR_ID); + + BlockTranslator1_17_0.init(); BLOCKS_JSON = null; // We no longer require this so let it garbage collect away } @@ -294,7 +300,6 @@ public BlockTranslator(String paletteFile) { if (waterlogged) { bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); - WATERLOGGED.add(javaRuntimeId); } else { bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } @@ -330,7 +335,8 @@ public BlockTranslator(String paletteFile) { // Loop around again to find all item frame runtime IDs for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { - if (entry.getKey().getString("name").equals("minecraft:frame")) { + String name = entry.getKey().getString("name"); + if (name.equals("minecraft:frame") || name.equals("minecraft:glow_frame")) { itemFrames.put(entry.getKey(), entry.getIntValue()); } } @@ -457,18 +463,11 @@ public static BiMap getJavaIdBlockMap() { } /** - * Get the item a Java client would receive when pressing - * the Pick Block key on a specific Java block state. - * - * @param javaId The Java runtime id of the block - * @return The Java identifier of the item + * @param javaRuntimeId the Java runtime ID of the block to search for. + * @return the corresponding block mapping for this runtime ID. */ - public static String getPickItem(int javaId) { - String itemIdentifier = JAVA_RUNTIME_ID_TO_PICK_ITEM.get(javaId); - if (itemIdentifier == null) { - return JAVA_ID_BLOCK_MAP.inverse().get(javaId).split("\\[")[0]; - } - return itemIdentifier; + public static BlockMapping getBlockMapping(int javaRuntimeId) { + return JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.getOrDefault(javaRuntimeId, BlockMapping.AIR); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java index 58861cb9c85..d86d0e71f21 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_17_0.java @@ -25,11 +25,11 @@ package org.geysermc.connector.network.translators.world.block; -public class BlockTranslator1_16_210 extends BlockTranslator { - public static final BlockTranslator1_16_210 INSTANCE = new BlockTranslator1_16_210(); +public class BlockTranslator1_17_0 extends BlockTranslator { + public static final BlockTranslator1_17_0 INSTANCE = new BlockTranslator1_17_0(); - public BlockTranslator1_16_210() { - super("bedrock/blockpalette.1_16_210.nbt"); + public BlockTranslator1_17_0() { + super("bedrock/block_palette.1_17_0.nbt"); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 12c0c9b6422..7824f1e5ba1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -33,11 +33,6 @@ @BlockEntity(name = "Banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getBannerColor(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { int bannerColor = BlockStateValues.getBannerColor(blockState); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 28cb52f6447..f82bab99ef8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -31,11 +31,6 @@ @BlockEntity(name = "Bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getBedColor(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte bedcolor = BlockStateValues.getBedColor(blockState); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index 2417f1e6e0f..5b96dc70d0c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -32,7 +32,14 @@ /** * Implemented only if a block is a block entity in Bedrock and not Java Edition. */ -public interface BedrockOnlyBlockEntity { +public interface BedrockOnlyBlockEntity extends RequiresBlockState { + /** + * Determines if block is part of class + * @param blockState BlockState to be compared + * @return true if part of the class + */ + boolean isBlock(int blockState); + /** * Update the block on Bedrock Edition. * @param session GeyserSession. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index f109ed6b4e4..983a3d06d0a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -35,7 +35,6 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -47,10 +46,9 @@ public abstract class BlockEntityTranslator { public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); /** - * 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 chunks on as we don't need to double-cache data + * A list of all block entities that only exist on Bedrock */ - public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + public static final ObjectArrayList BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>(); /** * Contains a list of irregular block entity name translations that can't be fit into the regex @@ -81,23 +79,17 @@ public static void init() { try { BLOCK_ENTITY_TRANSLATORS.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity" + clazz.getCanonicalName()); } } - boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { - GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); + for (Class clazz : ref.getSubTypesOf(BedrockOnlyBlockEntity.class)) { + GeyserConnector.getInstance().getLogger().debug("Found Bedrock-only block entity: " + clazz.getCanonicalName()); try { - RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); - if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { - // Not needed to put this one in the map; cache chunks takes care of that for us - GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); - continue; - } - REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); + BedrockOnlyBlockEntity bedrockOnlyBlockEntity = (BedrockOnlyBlockEntity) clazz.newInstance(); + BEDROCK_ONLY_BLOCK_ENTITIES.add(bedrockOnlyBlockEntity); } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block state " + clazz.getCanonicalName()); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index a4bb3e691c7..08c108b78ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -54,9 +54,4 @@ public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) builder.put("LastExecution", (long) 0); } } - - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getCommandBlockValues().containsKey(blockState); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index 9775017fb7b..7cda4f6196a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -37,7 +37,7 @@ * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ @BlockEntity(name = "Chest") -public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity { @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 062fd49228b..1fa6cab02f5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -33,7 +33,7 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.utils.BlockEntityUtils; -public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity { /** * @param blockState the Java block state of a potential flower pot block * @return true if the block is a flower pot diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java index 3254565d10f..a0c1033c529 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -30,21 +30,14 @@ import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.utils.ChunkUtils; /** * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ -public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getNoteblockPitch(blockState) != -1; - } +public class NoteblockBlockEntityTranslator { public static void translate(GeyserSession session, Position position) { - int blockState = session.getConnector().getConfig().isCacheChunks() ? - session.getConnector().getWorldManager().getBlockAt(session, position) : - ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position); + int blockState = session.getConnector().getWorldManager().getBlockAt(session, position); BlockEventPacket blockEventPacket = new BlockEventPacket(); blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java index 146e024f21f..76fb732b7aa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java @@ -29,12 +29,4 @@ * Implemented in block entities if their Java block state is required for additional values in Bedrock */ public interface RequiresBlockState { - - /** - * Determines if block is part of class - * @param blockState BlockState to be compared - * @return true if part of the class - */ - boolean isBlock(int blockState); - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 04d58fcced8..a6ea24f7cea 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -32,7 +32,7 @@ import javax.annotation.Nullable; @BlockEntity(name = "ShulkerBox") -public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { +public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { /** * Also used in {@link org.geysermc.connector.network.translators.inventory.translators.ShulkerInventoryTranslator} * where {@code tag} is passed as null. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index 61ca4fa9c1d..ad6c32d4fd4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -96,6 +96,7 @@ private String getBedrockSignColor(String javaColor) { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + //TODO Bedrock 1.17.10 glow text StringBuilder signText = new StringBuilder(); for (int i = 0; i < 4; i++) { int currentLine = i + 1; @@ -132,7 +133,7 @@ public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) signText.append(getBedrockSignColor(color.getValue().toString())); } - signText.append(finalSignLine.toString()); + signText.append(finalSignLine); signText.append("\n"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index b3e25b21267..e7139f20cac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -50,11 +50,6 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { public static boolean ALLOW_CUSTOM_SKULLS; - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getSkullVariant(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte skullVariant = BlockStateValues.getSkullVariant(blockState); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 672fa1a354a..faf8d6dc8be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -62,10 +62,6 @@ private static int getPaletteHeader(BitArrayVersion version, boolean runtime) { return (version.getId() << 1) | (runtime ? 1 : 0); } - private static BitArrayVersion getVersionFromHeader(byte header) { - return BitArrayVersion.get(header >> 1, true); - } - public int getFullBlock(int index) { return this.palette.getInt(this.bitArray.get(index)); } diff --git a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java index a8af51bf5a9..a1a2d474b59 100644 --- a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java +++ b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java @@ -43,8 +43,7 @@ import java.util.concurrent.TimeUnit; public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { - - private GeyserConnector connector; + private final GeyserConnector connector; public GeyserLegacyPingPassthrough(GeyserConnector connector) { this.connector = connector; diff --git a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java new file mode 100644 index 00000000000..99045bb22a9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java @@ -0,0 +1,85 @@ +/* + * 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.connector.registry.type; + +import lombok.Builder; +import lombok.Value; + +import javax.annotation.Nullable; + +@Builder +@Value +public class BlockMapping { + public static BlockMapping AIR; + + String javaIdentifier; + /** + * The block ID shared between all different block states of this block. + * NOT the runtime ID! + */ + int javaBlockId; + + double hardness; + boolean canBreakWithHand; + /** + * The index of this collision in collision.json + */ + int collisionIndex; + @Nullable String pickItem; + + /** + * @return the identifier without the additional block states + */ + public String getCleanJavaIdentifier() { + return javaIdentifier.split("\\[")[0]; + } + + /** + * @return the corresponding Java identifier for this item + */ + public String getItemIdentifier() { + if (pickItem != null && !pickItem.equals("minecraft:air")) { + // Spawners can have air as their pick item which we are not interested in. + return pickItem; + } + + return getCleanJavaIdentifier(); + } + + /** + * Get the item a Java client would receive when pressing + * the Pick Block key on a specific Java block state. + * + * @return The Java identifier of the item + */ + public String getPickItem() { + if (pickItem != null) { + return pickItem; + } + + return getCleanJavaIdentifier(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java new file mode 100644 index 00000000000..8c07cccbcd2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -0,0 +1,245 @@ +/* + * 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.connector.skin; + +import com.fasterxml.jackson.core.JsonProcessingException; +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.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.Constants; +import org.geysermc.connector.utils.PluginMessageUtils; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import javax.net.ssl.SSLException; +import java.net.ConnectException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import static org.geysermc.connector.utils.PluginMessageUtils.getSkinChannel; + +public final class FloodgateSkinUploader { + private final ObjectMapper JACKSON = new ObjectMapper(); + private final List skinQueue = new ArrayList<>(); + + private final GeyserLogger logger; + private final WebSocketClient client; + private volatile boolean closed; + + @Getter private int id; + @Getter private String verifyCode; + @Getter private int subscribersCount; + + public FloodgateSkinUploader(GeyserConnector connector) { + this.logger = connector.getLogger(); + this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) { + @Override + public void onOpen(ServerHandshake handshake) { + setConnectionLostTimeout(11); + + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } + } + + @Override + public void onMessage(String message) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(message); + if (node.has("error")) { + logger.error("Got an error: " + node.get("error").asText()); + return; + } + + int typeId = node.get("event_id").asInt(); + WebsocketEventType type = WebsocketEventType.getById(typeId); + if (type == null) { + logger.warning(String.format( + "Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!", + typeId)); + return; + } + + switch (type) { + case SUBSCRIBER_CREATED: + id = node.get("id").asInt(); + verifyCode = node.get("verify_code").asText(); + break; + case SUBSCRIBER_COUNT: + subscribersCount = node.get("subscribers_count").asInt(); + break; + case SKIN_UPLOADED: + // if Geyser is the only subscriber we have send it to the server manually + // otherwise it's handled by the Floodgate plugin subscribers + if (subscribersCount != 1) { + break; + } + + String xuid = node.get("xuid").asText(); + GeyserSession session = connector.getPlayerByXuid(xuid); + + if (session != null) { + if (!node.get("success").asBoolean()) { + logger.info("Failed to upload skin for " + session.getName()); + return; + } + + JsonNode data = node.get("data"); + + String value = data.get("value").asText(); + String signature = data.get("signature").asText(); + + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + } + break; + case LOG_MESSAGE: + String logMessage = node.get("message").asText(); + switch (node.get("priority").asInt()) { + case -1: + logger.debug("Got a message from skin uploader: " + logMessage); + break; + case 0: + logger.info("Got a message from skin uploader: " +logMessage); + break; + case 1: + logger.error("Got a message from skin uploader: " + logMessage); + break; + default: + logger.info(logMessage); + break; + } + break; + case NEWS_ADDED: + //todo + } + } catch (Exception e) { + logger.error("Error while receiving a message", e); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (reason != null && !reason.isEmpty()) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(reason); + // info means that the uploader itself did nothing wrong + if (node.has("info")) { + String info = node.get("info").asText(); + logger.debug("Got disconnected from the skin uploader: " + info); + } + // error means that the uploader did something wrong + if (node.has("error")) { + String error = node.get("error").asText(); + logger.info("Got disconnected from the skin uploader: " + error); + } + } catch (JsonProcessingException ignored) { + // ignore invalid json + } catch (Exception e) { + logger.error("Error while handling onClose", e); + } + } + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); + } + + @Override + public void onError(Exception ex) { + if (ex instanceof ConnectException || ex instanceof SSLException) { + if (logger.isDebug()) { + logger.error("[debug] Got an error", ex); + } + return; + } + logger.error("Got an error", ex); + } + }; + } + + public void uploadSkin(JsonNode chainData, String clientData) { + if (chainData == null || !chainData.isArray() || clientData == null) { + return; + } + + ObjectNode node = JACKSON.createObjectNode(); + node.set("chain_data", chainData); + node.put("client_data", clientData); + + // The reason why I don't like Jackson + String jsonString; + try { + jsonString = JACKSON.writeValueAsString(node); + } catch (Exception e) { + logger.error("Failed to upload skin", e); + return; + } + + if (client.isOpen()) { + client.send(jsonString); + return; + } + skinQueue.add(jsonString); + } + + private void reconnectLater(GeyserConnector connector) { + // we ca only reconnect when the thread pool is open + if (connector.getGeneralThreadPool().isShutdown() || closed) { + logger.info("The skin uploader has been closed"); + return; + } + + long additionalTime = ThreadLocalRandom.current().nextInt(7); + // we don't have to check the result. onClose will handle that for us + connector.getGeneralThreadPool() + .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); + } + + public FloodgateSkinUploader start() { + client.connect(); + return this; + } + + public void close() { + if (!closed) { + closed = true; + client.close(); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index 745a23ebd6e..6574c9350b1 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -86,7 +86,7 @@ public class SkinProvider { public static final String EARS_GEOMETRY_SLIM; public static final SkinGeometry SKULL_GEOMETRY; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { /* Load in the normal ears geometry */ @@ -521,7 +521,7 @@ private static BufferedImage readFiveZigCape(String url) throws IOException { return null; } - private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) { + public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) { BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); @@ -581,7 +581,6 @@ public static byte[] bufferedImageToImageData(BufferedImage image) { outputStream.write((rgba >> 24) & 0xFF); } } - return outputStream.toByteArray(); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java b/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java index c7727cd6292..95e0f809dcf 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java @@ -58,22 +58,17 @@ public enum BedrockMapIcon { private static final BedrockMapIcon[] VALUES = values(); - private MapIconType iconType; + private final MapIconType iconType; @Getter - private int iconID; + private final int iconID; - private int red; - private int green; - private int blue; + private final int red; + private final int green; + private final int blue; BedrockMapIcon(MapIconType iconType, int iconID) { - this.iconType = iconType; - this.iconID = iconID; - - this.red = 255; - this.green = 255; - this.blue = 255; + this(iconType, iconID, 255, 255, 255); } BedrockMapIcon(MapIconType iconType, int iconID, int red, int green, int blue) { @@ -107,11 +102,11 @@ public static BedrockMapIcon fromType(MapIconType iconType) { * @return ARGB as an int */ public int toARGB() { - int alpha = 255; + final int alpha = 255; return ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | - ((blue & 0xFF) << 0); + (blue & 0xFF); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index 0c570dae60e..d932c7d2401 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -32,6 +32,8 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; +import javax.annotation.Nonnull; + public class BlockEntityUtils { private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty"); @@ -66,11 +68,11 @@ public static BlockEntityTranslator getBlockEntityTranslator(String name) { return EMPTY_TRANSLATOR; } - public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) { + public static void updateBlockEntity(GeyserSession session, @Nonnull NbtMap blockEntity, Position position) { updateBlockEntity(session, blockEntity, Vector3i.from(position.getX(), position.getY(), position.getZ())); } - public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Vector3i position) { + public static void updateBlockEntity(GeyserSession session, @Nonnull NbtMap blockEntity, Vector3i position) { BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); blockEntityPacket.setBlockPosition(position); blockEntityPacket.setData(blockEntity); diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 997e4aee434..0ad3f01482a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -29,10 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ToolItemEntry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.registry.type.BlockMapping; public class BlockUtils { /** @@ -40,16 +43,28 @@ public class BlockUtils { */ public static final Position POSITION_ZERO = new Position(0, 0, 0); - private static boolean correctTool(String blockToolType, String itemToolType) { - return (blockToolType.equals("sword") && itemToolType.equals("sword")) || - (blockToolType.equals("shovel") && itemToolType.equals("shovel")) || - (blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) || - (blockToolType.equals("axe") && itemToolType.equals("axe")) || - (blockToolType.equals("shears") && itemToolType.equals("shears")); + private static boolean correctTool(GeyserSession session, BlockMapping blockMapping, String itemToolType) { + switch (itemToolType) { + case "axe": + return session.getTagCache().isAxeEffective(blockMapping); + case "hoe": + return session.getTagCache().isHoeEffective(blockMapping); + case "pickaxe": + return session.getTagCache().isPickaxeEffective(blockMapping); + case "shears": + return session.getTagCache().isShearsEffective(blockMapping); + case "shovel": + return session.getTagCache().isShovelEffective(blockMapping); + case "sword": + return blockMapping.getJavaBlockId() == BlockTranslator.JAVA_COBWEB_BLOCK_ID; + default: + session.getConnector().getLogger().warning("Unknown tool type: " + itemToolType); + return false; + } } - private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) { - if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; + private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isShearsEffective) { + if (toolType.equals("shears")) return isShearsEffective ? 5.0 : 15.0; if (toolType.equals("")) return 1.0; switch (toolTier) { // https://minecraft.gamepedia.com/Breaking#Speed @@ -70,18 +85,41 @@ private static double toolBreakTimeBonus(String toolType, String toolTier, boole } } - //http://minecraft.gamepedia.com/Breaking - private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, - String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, + private static boolean canToolTierBreakBlock(GeyserSession session, BlockMapping blockMapping, String toolTier) { + if (toolTier.equals("netherite") || toolTier.equals("diamond")) { + // As of 1.17, these tiers can mine everything that is mineable + return true; + } + + switch (toolTier) { + // Use intentional fall-throughs to check each tier with this block + default: + if (session.getTagCache().requiresStoneTool(blockMapping)) { + return false; + } + case "stone": + if (session.getTagCache().requiresIronTool(blockMapping)) { + return false; + } + case "iron": + if (session.getTagCache().requiresDiamondTool(blockMapping)) { + return false; + } + } + + return true; + } + + // https://minecraft.gamepedia.com/Breaking + private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, boolean canTierMineBlock, + String toolType, boolean isShearsEffective, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) { - double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; + double baseTime = (((correctTool && canTierMineBlock) || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; double speed = 1.0 / baseTime; if (correctTool) { - speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock); + speed *= toolBreakTimeBonus(toolType, toolTier, isShearsEffective); speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; - } else if (toolType.equals("sword")) { - speed*= (isCobweb ? 15.0 : 1.5); } speed *= 1.0 + (0.2 * hasteLevel); @@ -108,29 +146,32 @@ private static double calculateBreakTime(double blockHardness, String toolTier, return 1.0 / speed; } - public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, GeyserSession session) { - boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId); - boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID; - String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, ""); - boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId); + public static double getBreakTime(GeyserSession session, BlockMapping blockMapping, ItemEntry item, CompoundTag nbtData, boolean isSessionPlayer) { + boolean isShearsEffective = session.getTagCache().isShearsEffective(blockMapping); //TODO called twice + boolean canHarvestWithHand = blockMapping.isCanBreakWithHand(); String toolType = ""; String toolTier = ""; boolean correctTool = false; + boolean toolCanBreak = false; if (item instanceof ToolItemEntry) { ToolItemEntry toolItem = (ToolItemEntry) item; toolType = toolItem.getToolType(); toolTier = toolItem.getToolTier(); - correctTool = correctTool(blockToolType, toolType); + correctTool = correctTool(session, blockMapping, toolType); + toolCanBreak = canToolTierBreakBlock(session, blockMapping, toolTier); } int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); int hasteLevel = 0; int miningFatigueLevel = 0; - if (session == null) { - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); + if (!isSessionPlayer) { + // Another entity is currently mining; we have all the information we know + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, + toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, + false, false); } - hasteLevel = session.getEffectCache().getEffectLevel(Effect.FASTER_DIG); + hasteLevel = Math.max(session.getEffectCache().getEffectLevel(Effect.FASTER_DIG), session.getEffectCache().getEffectLevel(Effect.CONDUIT_POWER)); miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); boolean isInWater = session.getCollisionManager().isPlayerInWater(); @@ -140,7 +181,24 @@ public static double getBreakTime(double blockHardness, int blockId, ItemEntry i boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective, + toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, + outOfWaterButNotOnGround, insideWaterNotOnGround); + } + + public static double getSessionBreakTime(GeyserSession session, BlockMapping blockMapping) { + PlayerInventory inventory = session.getPlayerInventory(); + GeyserItemStack item = inventory.getItemInHand(); + ItemEntry itemEntry; + CompoundTag nbtData; + if (item != null) { + itemEntry = item.getItemEntry(); + nbtData = item.getNbt(); + } else { + itemEntry = null; + nbtData = new CompoundTag(""); + } + return getBreakTime(session, blockMapping, itemEntry, nbtData, true); } /** diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index b6e38723736..85b7f860066 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector2i; @@ -42,12 +43,9 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Data; import lombok.experimental.UtilityClass; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; @@ -56,7 +54,6 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; -import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; import org.geysermc.connector.network.translators.world.chunk.BlockStorage; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; @@ -72,18 +69,21 @@ @UtilityClass public class ChunkUtils { /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively. - * Not used if cache chunks is enabled + * The minimum height Bedrock Edition will accept. */ - public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); + private static final int MINIMUM_ACCEPTED_HEIGHT = 0; + /** + * The maximum height Bedrock Edition will accept. + */ + private static final int MAXIMUM_ACCEPTED_HEIGHT = 256; private static int indexYZXtoXZY(int yzx) { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); } - public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { + public static ChunkData translateToBedrock(GeyserSession session, Column column, int yOffset) { Chunk[] javaSections = column.getChunks(); - ChunkSection[] sections = new ChunkSection[javaSections.length]; + ChunkSection[] sections = new ChunkSection[javaSections.length - yOffset]; // Temporarily stores compound tags of Bedrock-only block entities List bedrockOnlyBlockEntities = new ArrayList<>(); @@ -91,45 +91,16 @@ public static ChunkData translateToBedrock(GeyserSession session, Column column, BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); - boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasOwnChunkCache(); - - // If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager - boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache; - Chunk temporarySection = null; - for (int sectionY = 0; sectionY < javaSections.length; sectionY++) { - Chunk javaSection = javaSections[sectionY]; - - // Section is null, the cache will not contain anything of use - if (javaSection == null) { - // The column parameter contains all data currently available from the cache. If the chunk is null and the world manager - // reports the ability to access more data than the cache, attempt to fetch from the world manager instead. - if (shouldCheckWorldManagerOnMissingSections) { - // Ensure that temporary chunk is set - if (temporarySection == null) { - temporarySection = new Chunk(); - } - - // Read block data in section - session.getConnector().getWorldManager().getBlocksInSection(session, column.getX(), sectionY, column.getZ(), temporarySection); - - if (temporarySection.isEmpty()) { - // The world manager only contains air for the given section - // We can leave temporarySection as-is to allow it to potentially be re-used for later sections - continue; - } else { - javaSection = temporarySection; - - // Section contents have been modified, we can't re-use it - temporarySection = null; - } - } else { - continue; - } + if (yOffset < 0 && sectionY < -yOffset) { + // Ignore this chunk since it goes below the accepted height limit + continue; } + Chunk javaSection = javaSections[sectionY]; + // No need to encode an empty section... - if (javaSection.isEmpty()) { + if (javaSection == null || javaSection.isEmpty()) { continue; } @@ -157,7 +128,7 @@ public static ChunkData translateToBedrock(GeyserSession session, Column column, )); } } - sections[sectionY] = section; + sections[sectionY + yOffset] = section; continue; } @@ -230,7 +201,7 @@ public static ChunkData translateToBedrock(GeyserSession session, Column column, layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } - sections[sectionY] = new ChunkSection(layers); + sections[sectionY + yOffset] = new ChunkSection(layers); } CompoundTag[] blockEntities = column.getTileEntities(); @@ -263,7 +234,7 @@ public static ChunkData translateToBedrock(GeyserSession session, Column column, // Get Java blockstate ID from block entity position int blockState = 0; - Chunk section = column.getChunks()[pos.getY() >> 4]; + Chunk section = column.getChunks()[(pos.getY() >> 4) - yOffset]; if (section != null) { blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); } @@ -329,21 +300,15 @@ public static void updateBlock(GeyserSession session, int blockState, Position p */ public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed - long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); - if (frameEntityId != -1) { - // TODO: Very occasionally the item frame doesn't sync up when destroyed - Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it - ((ItemFrameEntity) entity).updateBlock(session); + ItemFrameEntity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, position); + if (itemFrameEntity != null) { + if (blockState == JAVA_AIR_ID) { // Item frame is still present and no block overrides that; refresh it + itemFrameEntity.updateBlock(session); + // Still update the chunk cache with the new block + session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState); return; } - - // Otherwise the item frame is gone - if (entity != null) { - session.getEntityCache().removeEntity(entity, false); - } else { - ItemFrameEntity.removePosition(session, position); - } + // 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); @@ -397,20 +362,12 @@ public static void updateBlock(GeyserSession session, int blockState, Vector3i p return newLecternHasBook; }); - // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag - // This is the only place I could find that interacts with the Java block state and block updates - // Iterates through all block entity translators and determines if the block state needs to be saved - for (RequiresBlockState requiresBlockState : BlockEntityTranslator.REQUIRES_BLOCK_STATE_LIST) { - if (requiresBlockState.isBlock(blockState)) { + // Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet + // needs to be sent + for (BedrockOnlyBlockEntity bedrockOnlyBlockEntity : BlockEntityTranslator.BEDROCK_ONLY_BLOCK_ENTITIES) { + if (bedrockOnlyBlockEntity.isBlock(blockState)) { // Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks - if (requiresBlockState instanceof BedrockOnlyBlockEntity) { - ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); - break; - } - if (!session.getConnector().getConfig().isCacheChunks()) { - // Blocks aren't saved to a chunk cache; resort to this smaller cache - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); - } + bedrockOnlyBlockEntity.updateBlock(session, blockState, position); break; //No block will be a part of two classes } } @@ -442,10 +399,31 @@ public static void sendEmptyChunks(GeyserSession session, Vector3i position, int } } + /** + * Process the minimum and maximum heights for this dimension + */ + public static void applyDimensionHeight(GeyserSession session, CompoundTag dimensionTag) { + int minY = ((IntTag) dimensionTag.get("min_y")).getValue(); + int maxY = ((IntTag) dimensionTag.get("height")).getValue(); + // Logical height can be ignored probably - seems to be for artificial limits like the Nether. + + if (minY % 16 != 0) { + throw new RuntimeException("Minimum Y must be a multiple of 16!"); + } + if (maxY % 16 != 0) { + throw new RuntimeException("Maximum Y must be a multiple of 16!"); + } + + if (minY < MINIMUM_ACCEPTED_HEIGHT || maxY > MAXIMUM_ACCEPTED_HEIGHT) { + session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds")); + } + + session.getChunkCache().setMinY(minY); + } + @Data public static final class ChunkData { private final ChunkSection[] sections; - private final NbtMap[] blockEntities; } } diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java similarity index 66% rename from common/src/main/java/org/geysermc/common/window/button/FormButton.java rename to connector/src/main/java/org/geysermc/connector/utils/Constants.java index d2075d3ff46..02f5c1ae404 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -23,35 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.connector.utils; -import lombok.Getter; -import lombok.Setter; +import java.net.URI; +import java.net.URISyntaxException; -public class FormButton { +public final class Constants { + public static final URI GLOBAL_API_WS_URI; + public static final String NTP_SERVER = "time.cloudflare.com"; - @Getter - @Setter - private String text; + public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news"; + public static final String NEWS_PROJECT_NAME = "geyser"; - @Getter - private FormImage image; - - public FormButton(String text) { - this.text = text; - } - - public FormButton(String text, FormImage image) { - this.text = text; - - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } - } - - public void setImage(FormImage image) { - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; + static { + URI wsUri = null; + try { + wsUri = new URI("wss://api.geysermc.org/ws"); + } catch (URISyntaxException e) { + e.printStackTrace(); } + GLOBAL_API_WS_URI = wsUri; } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java index 0b5c2bdd3d4..43675ebfd78 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java @@ -28,6 +28,7 @@ import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.PreferencesCache; import java.util.concurrent.TimeUnit; @@ -36,18 +37,25 @@ * Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind */ public class CooldownUtils { - private static CooldownType SHOW_COOLDOWN; + private static CooldownType DEFAULT_SHOW_COOLDOWN; - public static void setShowCooldown(String showCooldown) { - SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + public static void setDefaultShowCooldown(String showCooldown) { + DEFAULT_SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + } + + public static CooldownType getDefaultShowCooldown() { + return DEFAULT_SHOW_COOLDOWN; } /** - * Starts sending the fake cooldown to the Bedrock client. + * Starts sending the fake cooldown to the Bedrock client. If the cooldown is not disabled, the sent type is the cooldownPreference in {@link PreferencesCache} * @param session GeyserSession */ public static void sendCooldown(GeyserSession session) { - if (SHOW_COOLDOWN == CooldownType.DISABLED) return; + if (DEFAULT_SHOW_COOLDOWN == CooldownType.DISABLED) return; + CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); + 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 SetTitlePacket titlePacket = new SetTitlePacket(); @@ -56,19 +64,20 @@ public static void sendCooldown(GeyserSession session) { session.sendUpstreamPacket(titlePacket); session.setLastHitTime(System.currentTimeMillis()); long lastHitTime = session.getLastHitTime(); // Used later to prevent multiple scheduled cooldown threads - computeCooldown(session, lastHitTime); + computeCooldown(session, sessionPreference, lastHitTime); } /** * Keeps updating the cooldown until the bar is complete. * @param session GeyserSession + * @param sessionPreference The type of cooldown the client prefers * @param lastHitTime The time of the last hit. Used to gauge how long the cooldown is taking. */ - private static void computeCooldown(GeyserSession session, long lastHitTime) { + private static void computeCooldown(GeyserSession session, CooldownType sessionPreference, long lastHitTime) { if (session.isClosed()) return; // Don't run scheduled tasks if the client left if (lastHitTime != session.getLastHitTime()) return; // Means another cooldown has started so there's no need to continue this one SetTitlePacket titlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { titlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -79,10 +88,10 @@ private static void computeCooldown(GeyserSession session, long lastHitTime) { titlePacket.setStayTime(2); session.sendUpstreamPacket(titlePacket); if (hasCooldown(session)) { - session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -133,7 +142,7 @@ public enum CooldownType { public static final CooldownType[] VALUES = values(); /** - * Convert the CooldownType string (from config) to the enum, TITLE on fail + * Convert the CooldownType string (from config) to the enum, DISABLED on fail * * @param name CooldownType string * diff --git a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java index eb712b13568..b5b0d24cafc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java @@ -26,9 +26,16 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.Effect; +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.connector.entity.Entity; +import org.geysermc.connector.entity.living.ArmorStandEntity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class EntityUtils { +public final class EntityUtils { /** * Convert Java edition effect IDs to Bedrock edition @@ -72,4 +79,147 @@ public static EntityType toBedrockEntity(com.github.steveice10.mc.protocol.data. return null; } } + + private static float getMountedHeightOffset(Entity mount) { + float height = mount.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); + float mountedHeightOffset = height * 0.75f; + switch (mount.getEntityType()) { + case CHICKEN: + case SPIDER: + mountedHeightOffset = height * 0.5f; + break; + case DONKEY: + case MULE: + mountedHeightOffset -= 0.25f; + break; + case LLAMA: + mountedHeightOffset = height * 0.67f; + break; + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + mountedHeightOffset = 0; + break; + case BOAT: + mountedHeightOffset = -0.1f; + break; + case HOGLIN: + case ZOGLIN: + boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); + mountedHeightOffset = height - (isBaby ? 0.2f : 0.15f); + break; + case PIGLIN: + mountedHeightOffset = height * 0.92f; + break; + case RAVAGER: + mountedHeightOffset = 2.1f; + break; + case SKELETON_HORSE: + mountedHeightOffset -= 0.1875f; + break; + case STRIDER: + mountedHeightOffset = height - 0.19f; + break; + } + return mountedHeightOffset; + } + + private static float getHeightOffset(Entity passenger) { + boolean isBaby; + switch (passenger.getEntityType()) { + case SKELETON: + case STRAY: + case WITHER_SKELETON: + return -0.6f; + case ARMOR_STAND: + if (((ArmorStandEntity) passenger).isMarker()) { + return 0.0f; + } else { + return 0.1f; + } + case ENDERMITE: + case SILVERFISH: + return 0.1f; + case PIGLIN: + case PIGLIN_BRUTE: + case ZOMBIFIED_PIGLIN: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? -0.05f : -0.45f; + case ZOMBIE: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? 0.0f : -0.45f; + case EVOKER: + case ILLUSIONER: + case PILLAGER: + case RAVAGER: + case VINDICATOR: + case WITCH: + return -0.45f; + case PLAYER: + return -0.35f; + } + if (passenger instanceof AnimalEntity) { + return 0.14f; + } + return 0f; + } + + /** + * Adjust an entity's height if they have mounted/dismounted an entity. + */ + public static void updateMountOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { + passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); + if (riding) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float mountedHeightOffset = getMountedHeightOffset(mount); + float heightOffset = getHeightOffset(passenger); + + float xOffset = 0; + float yOffset = mountedHeightOffset + heightOffset; + float zOffset = 0; + switch (mount.getEntityType()) { + case BOAT: + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + xOffset = 0.2f; + } else if (moreThanOneEntity) { + xOffset = -0.6f; + } + break; + case CHICKEN: + zOffset = -0.1f; + break; + case LLAMA: + zOffset = -0.3f; + break; + } + /* + * Bedrock Differences + * Zoglin & Hoglin seem to be taller in Bedrock edition + * Horses are tinier + * Players, Minecarts, and Boats have different origins + */ + if (passenger.getEntityType() == EntityType.PLAYER && mount.getEntityType() != EntityType.PLAYER) { + yOffset += EntityType.PLAYER.getOffset(); + } + switch (mount.getEntityType()) { + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + case BOAT: + yOffset -= mount.getEntityType().getHeight() * 0.5f; + } + Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); + passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); + } + passenger.updateBedrockMetadata(session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 1715eb4eb00..76015175ddb 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -157,7 +157,7 @@ public static InputStream getResource(String resource) { ResourceReadEvent event = EventManager.getInstance().triggerEvent(new ResourceReadEvent(resource, stream)).getEvent(); if (event.getInputStream() == null) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.resource", resource)); + throw new AssertionError("Unable to find resource: " + resource); } return event.getInputStream(); } @@ -210,7 +210,8 @@ public static Reflections getReflections(String path) { URL resource = FileUtils.class.getClassLoader().getResource("META-INF/reflections/" + path + "-reflections.xml"); try (InputStream inputStream = resource.openConnection().getInputStream()) { reflections.merge(serializer.read(inputStream)); - } catch (IOException e) { } + } catch (IOException ignored) { + } return reflections; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/GameRule.java b/connector/src/main/java/org/geysermc/connector/utils/GameRule.java index a4e4ef231fc..0b3908694fd 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/GameRule.java +++ b/connector/src/main/java/org/geysermc/connector/utils/GameRule.java @@ -51,6 +51,7 @@ public enum GameRule { DROWNINGDAMAGE("drowningDamage", Boolean.class, true), FALLDAMAGE("fallDamage", Boolean.class, true), FIREDAMAGE("fireDamage", Boolean.class, true), + FREEZEDAMAGE("freezeDamage", Boolean.class, true), FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only KEEPINVENTORY("keepInventory", Boolean.class, false), LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only @@ -58,6 +59,7 @@ public enum GameRule { MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only MOBGRIEFING("mobGriefing", Boolean.class, true), NATURALREGENERATION("naturalRegeneration", Boolean.class, true), + PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", Integer.class, 100), // JE only RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3), REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true), @@ -68,16 +70,16 @@ public enum GameRule { UNKNOWN("unknown", Object.class); - private static final GameRule[] VALUES = values(); + public static final GameRule[] VALUES = values(); @Getter - private String javaID; + private final String javaID; @Getter - private Class type; + private final Class type; @Getter - private Object defaultValue; + private final Object defaultValue; GameRule(String javaID, Class type) { this(javaID, type, null); diff --git a/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java b/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java index 0b59efff334..1d6cf60d99a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java @@ -25,12 +25,12 @@ package org.geysermc.connector.utils; -import com.google.common.collect.ImmutableSet; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import lombok.Getter; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.living.animal.horse.HorseEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -40,20 +40,6 @@ import java.util.Set; public class InteractiveTagManager { - /** - * A list of all foods a horse/donkey can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final Set DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple", - "golden_carrot", "sugar", "apple", "wheat", "hay_block"); - - /** - * A list of all flowers. Used for feeding bees. - */ - private static final Set FLOWERS = ImmutableSet.of("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", - "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", - "sunflower", "lilac", "rose_bush", "peony"); - /** * All entity types that can be leashed on Java Edition */ @@ -66,14 +52,6 @@ public class InteractiveTagManager { private static final Set SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, EntityType.ZOMBIE_HORSE, EntityType.MULE); - /** - * A list of all foods a wolf can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final Set WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken", - "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", - "cooked_rabbit"); - /** * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") * @@ -85,9 +63,8 @@ public static void updateTag(GeyserSession session, Entity interactEntity) { ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry(); String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen - // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food InteractiveTag interactiveTag = InteractiveTag.NONE; + if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { // Unleash the entity interactiveTag = InteractiveTag.REMOVE_LEASH; @@ -105,31 +82,24 @@ public static void updateTag(GeyserSession session, Entity interactEntity) { // 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(session, javaIdentifierStripped, itemEntry)) { + // This animal can be fed + interactiveTag = InteractiveTag.FEED; } else { switch (interactEntity.getEntityType()) { - case BEE: - if (FLOWERS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - } - break; case BOAT: - interactiveTag = InteractiveTag.BOARD_BOAT; + if (interactEntity.getPassengers().size() < 2) { + interactiveTag = InteractiveTag.BOARD_BOAT; + } break; case CAT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { // Tamed and owned by player - can sit/stand interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; break; } break; - case CHICKEN: - if (javaIdentifierStripped.contains("seeds")) { - interactiveTag = InteractiveTag.FEED; - } - break; case MOOSHROOM: // Shear the mooshroom if (javaIdentifierStripped.equals("shears")) { @@ -143,9 +113,7 @@ else if (javaIdentifierStripped.equals("bowl")) { } // Fall down to COW as this works on mooshrooms case COW: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (javaIdentifierStripped.equals("bucket")) { + if (javaIdentifierStripped.equals("bucket")) { // Milk the cow interactiveTag = InteractiveTag.MILK; } @@ -175,69 +143,28 @@ else if (javaIdentifierStripped.equals("bowl")) { interactiveTag = InteractiveTag.OPEN_CONTAINER; break; } - // have another switch statement as, while these share mount attributes they don't share food - switch (interactEntity.getEntityType()) { - case LLAMA: - case TRADER_LLAMA: - if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { - interactiveTag = InteractiveTag.FEED; - break; - } - case DONKEY: - case HORSE: - // Undead can't eat - if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - break; - } - } if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { // Can't ride a baby if (tamed) { interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (itemEntry.equals(ItemEntry.AIR)) { + } else if (itemEntry.getJavaId() == 0) { // Can't hide an untamed entity without having your hand empty interactiveTag = InteractiveTag.MOUNT; } } break; - case FOX: - if (javaIdentifierStripped.equals("sweet_berries")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case HOGLIN: - if (javaIdentifierStripped.equals("crimson_fungus")) { - interactiveTag = InteractiveTag.FEED; - } - break; case MINECART: - interactiveTag = InteractiveTag.RIDE_MINECART; + if (interactEntity.getPassengers().isEmpty()) { + interactiveTag = InteractiveTag.RIDE_MINECART; + } break; case MINECART_CHEST: case MINECART_COMMAND_BLOCK: case MINECART_HOPPER: interactiveTag = InteractiveTag.OPEN_CONTAINER; break; - case OCELOT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PANDA: - if (javaIdentifierStripped.equals("bamboo")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PARROT: - if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { - interactiveTag = InteractiveTag.FEED; - } - break; case PIG: - if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { interactiveTag = InteractiveTag.MOUNT; } break; @@ -246,15 +173,8 @@ else if (javaIdentifierStripped.equals("bowl")) { interactiveTag = InteractiveTag.BARTER; } break; - case RABBIT: - if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { - interactiveTag = InteractiveTag.FEED; - } - break; case SHEEP: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { + if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { if (javaIdentifierStripped.equals("shears")) { // Shear the sheep interactiveTag = InteractiveTag.SHEAR; @@ -265,17 +185,10 @@ else if (javaIdentifierStripped.equals("bowl")) { } break; case STRIDER: - if (javaIdentifierStripped.equals("warped_fungus")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { interactiveTag = InteractiveTag.RIDE_STRIDER; } break; - case TURTLE: - if (javaIdentifierStripped.equals("seagrass")) { - interactiveTag = InteractiveTag.FEED; - } - break; case VILLAGER: if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby @@ -289,10 +202,6 @@ else if (javaIdentifierStripped.equals("bowl")) { if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { // Bone and untamed - can tame interactiveTag = InteractiveTag.TAME; - } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { - // Compatible food in hand - feed - // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this - interactiveTag = InteractiveTag.FEED; } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { // Tamed and owned by player - can sit/stand diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index d4759acaa52..d9480d4666a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -170,6 +170,60 @@ public static ItemData createUnusableSpaceBlock(String description) { .tag(root.build()).build(); } + /** + * See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}. + * + * @param session the Bedrock client's session + * @param itemStack the item to try to find a match for. NBT will also be accounted for. + */ + public static void findOrCreateItem(GeyserSession session, ItemStack itemStack) { + PlayerInventory inventory = session.getPlayerInventory(); + + if (itemStack == null || itemStack.getId() == 0) { + return; + } + + // Check hotbar for item + for (int i = 36; i < 45; i++) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { + continue; + } + // If this is the item we're looking for + if (geyserItem.getJavaId() == itemStack.getId() && Objects.equals(geyserItem.getNbt(), itemStack.getNbt())) { + setHotbarItem(session, i); + // Don't check inventory if item was in hotbar + return; + } + } + + // Check inventory for item + for (int i = 9; i < 36; i++) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { + continue; + } + // If this is the item we're looking for + if (geyserItem.getJavaId() == itemStack.getId() && Objects.equals(geyserItem.getNbt(), itemStack.getNbt())) { + ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item + session.sendDownstreamPacket(packetToSend); + return; + } + } + + // If we still have not found the item, and we're in creative, ask for the item from the server. + if (session.getGameMode() == GameMode.CREATIVE) { + int slot = findEmptyHotbarSlot(inventory); + + ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, + itemStack); + if ((slot - 36) != inventory.getHeldItemSlot()) { + setHotbarItem(session, slot); + } + session.sendDownstreamPacket(actionPacket); + } + } + /** * Attempt to find the specified item name in the session's inventory. * If it is found and in the hotbar, set the user's held item to that slot. @@ -223,15 +277,7 @@ public static void findOrCreateItem(GeyserSession session, String itemName) { // If we still have not found the item, and we're in creative, ask for the item from the server. if (session.getGameMode() == GameMode.CREATIVE) { - int slot = inventory.getHeldItemSlot() + 36; - if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i).isEmpty()) { - slot = i; - break; - } - } - } + int slot = findEmptyHotbarSlot(inventory); ItemEntry entry = ItemRegistry.getItemEntry(itemName); if (entry != null) { @@ -247,6 +293,22 @@ public static void findOrCreateItem(GeyserSession session, String itemName) { } } + /** + * @return the first empty slot found in this inventory, or else the player's currently held slot. + */ + private static int findEmptyHotbarSlot(PlayerInventory inventory) { + int slot = inventory.getHeldItemSlot() + 36; + if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i).isEmpty()) { + slot = i; + break; + } + } + } + return slot; + } + /** * Changes the held item slot to the specified slot * @param session GeyserSession diff --git a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java index 070631645ef..dd4543f61e0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java @@ -1,47 +1,61 @@ -/* - * 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.connector.utils; - -import com.github.steveice10.opennbt.tag.builtin.*; - -public class ItemUtils { - - public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { - ListTag enchantments = (itemNBTData == null ? null : 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(); - } - } - return enchantmentLevel; - } - return 0; - } -} +/* + * 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.connector.utils; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.connector.network.translators.item.ItemRegistry; + +public class ItemUtils { + + public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { + ListTag enchantments = (itemNBTData == null ? null : 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(); + } + } + return enchantmentLevel; + } + return 0; + } + + /** + * @return the correct Bedrock durability for this item. + */ + public static int getCorrectBedrockDurability(int javaId, int original) { + if (javaId == ItemRegistry.FISHING_ROD.getJavaId()) { + // Java durability: 64 + // Bedrock durability : 384 + // 384 / 64 = 6 + return original * 6; + } + return original; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 5284bbcff17..5f5c1f17fe3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -60,7 +60,9 @@ public class LanguageUtils { public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); // Don't load the locale if it's already loaded. - if (LOCALE_MAPPINGS.containsKey(locale)) return; + if (LOCALE_MAPPINGS.containsKey(locale)) { + return; + } InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -113,7 +115,7 @@ public static String getPlayerLocaleString(String key, String locale, Object... // Try and get the key from the default locale if (formatString == null) { - properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale())); + properties = LOCALE_MAPPINGS.get(getDefaultLocale()); formatString = properties.getProperty(key); } @@ -125,7 +127,7 @@ public static String getPlayerLocaleString(String key, String locale, Object... // Final fallback if (formatString == null) { - formatString = key; + return key; } return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values); @@ -151,7 +153,10 @@ public static String formatLocale(String locale) { * @return the current default locale */ public static String getDefaultLocale() { - if (CACHED_LOCALE != null) return CACHED_LOCALE; // We definitely know the locale the user is using + if (CACHED_LOCALE != null) { + return CACHED_LOCALE; // We definitely know the locale the user is using + } + String locale; boolean isValid = true; if (GeyserConnector.getInstance() != null && diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index db5457244d5..7e32553b675 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -136,7 +136,7 @@ private static void downloadLocale(String locale) { // Check if we have already downloaded the locale file if (localeFile.exists()) { String curHash = ""; - String targetHash = ""; + String targetHash; if (locale.equals("en_us")) { try { diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 3c093df94cf..d3d5fa67d03 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -35,18 +35,17 @@ import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.window.*; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.ModalFormResponse; -import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; -import org.geysermc.connector.network.session.cache.WindowCache; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.ModalForm; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.CustomFormResponse; +import org.geysermc.cumulus.response.ModalFormResponse; +import org.geysermc.cumulus.response.SimpleFormResponse; import javax.crypto.SecretKey; import java.io.IOException; @@ -60,6 +59,8 @@ public class LoginEncryptionUtils { private static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + private static boolean HAS_SENT_ENCRYPTION_MESSAGE = false; + private static boolean validateChainData(JsonNode data) throws Exception { ECPublicKey lastKey = null; boolean validChain = false; @@ -71,7 +72,7 @@ private static boolean validateChainData(JsonNode data) throws Exception { } if (lastKey != null) { - if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; + if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; } JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString()); @@ -119,7 +120,8 @@ private static void encryptConnectionWithCert(GeyserConnector connector, GeyserS session.setAuthenticationData(new AuthData( extraData.get("displayName").asText(), UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText() + extraData.get("XUID").asText(), + certChainData, clientData )); if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { @@ -130,10 +132,23 @@ private static void encryptConnectionWithCert(GeyserConnector connector, GeyserS JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class)); + JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); + BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); + session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { - LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); + try { + LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); + } catch (Throwable e) { + // An error can be thrown on older Java 8 versions about an invalid key + if (connector.getConfig().isDebugMode()) { + e.printStackTrace(); + } + + sendEncryptionFailedMessage(connector); + } + } else { + sendEncryptionFailedMessage(connector); } } catch (Exception ex) { session.disconnect("disconnectionScreen.internalError.cantConnect"); @@ -155,132 +170,126 @@ private static void startEncryptionHandshake(GeyserSession session, PublicKey ke session.sendUpstreamPacketImmediately(packet); } - private static final int AUTH_MSA_DETAILS_FORM_ID = 1334; - private static final int AUTH_MSA_CODE_FORM_ID = 1335; - private static final int AUTH_FORM_ID = 1336; - private static final int AUTH_DETAILS_FORM_ID = 1337; + private static void sendEncryptionFailedMessage(GeyserConnector connector) { + if (!HAS_SENT_ENCRYPTION_MESSAGE) { + connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.encryption.line_1")); + connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.encryption.line_2", "https://geysermc.org/supported_java")); + HAS_SENT_ENCRYPTION_MESSAGE = true; + } + } - public static void showLoginWindow(GeyserSession session) { + public static void buildAndShowLoginWindow(GeyserSession session) { // Set DoDaylightCycle to false so the time doesn't accelerate while we're here session.setDaylightCycle(false); - String userLanguage = session.getLocale(); - SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); - if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) { - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage))); - } - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage))); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); + GeyserConfiguration config = session.getConnector().getConfig(); + boolean isPasswordAuthEnabled = config.getRemote().isPasswordAuthentication(); + + session.sendForm( + SimpleForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) + .title("geyser.auth.login.form.notice.title") + .content("geyser.auth.login.form.notice.desc") + .optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled) + .button("geyser.auth.login.form.notice.btn_login.microsoft") + .button("geyser.auth.login.form.notice.btn_disconnect") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + buildAndShowLoginWindow(session); + return; + } + + if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) { + session.setMicrosoftAccount(false); + buildAndShowLoginDetailsWindow(session); + return; + } + + if (isPasswordAuthEnabled && response.getClickedButtonId() == 1) { + session.setMicrosoftAccount(true); + buildAndShowMicrosoftAuthenticationWindow(session); + return; + } + + if (response.getClickedButtonId() == 0) { + // Just show the OAuth code + session.authenticateWithMicrosoftCode(); + return; + } - session.sendForm(window, AUTH_FORM_ID); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + })); } - public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getLocale(); - CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) - .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", "")) - .build(); + public static void buildAndShowLoginDetailsWindow(GeyserSession session) { + session.sendForm( + CustomForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) + .title("geyser.auth.login.form.details.title") + .label("geyser.auth.login.form.details.desc") + .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") + .input("geyser.auth.login.form.details.pass", "123456", "") + .responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + buildAndShowLoginDetailsWindow(session); + return; + } - session.sendForm(window, AUTH_DETAILS_FORM_ID); + session.authenticate(response.next(), response.next()); + })); } /** * Prompts the user between either OAuth code login or manual password authentication */ - public static void showMicrosoftAuthenticationWindow(GeyserSession session) { - String userLanguage = session.getLocale(); - SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), ""); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage))); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); - session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID); + public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { + session.sendForm( + SimpleForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) + .title("geyser.auth.login.form.notice.btn_login.microsoft") + .button("geyser.auth.login.method.browser") + .button("geyser.auth.login.method.password") + .button("geyser.auth.login.form.notice.btn_disconnect") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + buildAndShowLoginWindow(session); + return; + } + + if (response.getClickedButtonId() == 0) { + session.authenticateWithMicrosoftCode(); + } else if (response.getClickedButtonId() == 1) { + buildAndShowLoginDetailsWindow(session); + } else { + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); + } + })); } /** * Shows the code that a user must input into their browser */ - public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) { - ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + - response.user_code, "%gui.done", "%menu.disconnect"); - session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID); - } - - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { - WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(formId)) - return false; - - if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) { - FormWindow window = windowCache.getWindows().remove(formId); - window.setResponse(formData.trim()); - - if (!session.isLoggedIn()) { - if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(1); - String password = response.getInputResponses().get(2); - - session.authenticate(email, password); + public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) { + session.sendForm( + ModalForm.builder() + .title("%xbox.signin") + .content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code) + .button1("%gui.done") + .button2("%menu.disconnect") + .responseHandler((form, responseData) -> { + ModalFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + buildAndShowMicrosoftAuthenticationWindow(session); + return; + } - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); - } else { - showLoginDetailsWindow(session); - } - } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { - boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication(); - int microsoftButton = isPasswordAuthentication ? 1 : 0; - int disconnectButton = isPasswordAuthentication ? 2 : 1; - SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if (response != null) { - if (isPasswordAuthentication && response.getClickedButtonId() == 0) { - session.setMicrosoftAccount(false); - showLoginDetailsWindow(session); - } else if (response.getClickedButtonId() == microsoftButton) { - session.setMicrosoftAccount(true); - if (isPasswordAuthentication) { - showMicrosoftAuthenticationWindow(session); - } else { - // Just show the OAuth code - session.authenticateWithMicrosoftCode(); + if (response.getClickedButtonId() == 1) { + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } - } else if (response.getClickedButtonId() == disconnectButton) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); - } - } else { - showLoginWindow(session); - } - } else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) { - SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if (response != null) { - if (response.getClickedButtonId() == 0) { - session.authenticateWithMicrosoftCode(); - } else if (response.getClickedButtonId() == 1) { - showLoginDetailsWindow(session); - } else if (response.getClickedButtonId() == 2) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); - } - } else { - showLoginWindow(session); - } - } else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) { - ModalFormResponse response = (ModalFormResponse) window.getResponse(); - if (response != null) { - if (response.getClickedButtonId() == 1) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); - } - } else { - showMicrosoftAuthenticationWindow(session); - } - } - } - } - return true; + }) + ); } - } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java index f37527413e2..893aa0b8518 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java @@ -287,6 +287,6 @@ public int toABGR() { return ((alpha & 0xFF) << 24) | ((blue & 0xFF) << 16) | ((green & 0xFF) << 8) | - ((red & 0xFF) << 0); + (red & 0xFF); } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java new file mode 100644 index 00000000000..c70255f52e7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java @@ -0,0 +1,176 @@ +/* + * 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.connector.utils; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.floodgate.news.NewsItem; +import org.geysermc.floodgate.news.NewsItemAction; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class NewsHandler { + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private final GeyserLogger logger = GeyserConnector.getInstance().getLogger(); + private final Gson gson = new Gson(); + + private final Map activeNewsItems = new HashMap<>(); + private final String branch; + private final int build; + + private boolean firstCheck = true; + + public NewsHandler(String branch, int build) { + this.branch = branch; + this.build = build; + + executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES); + } + + private void schedule(long delayMs) { + executorService.schedule(this::checkNews, delayMs, TimeUnit.MILLISECONDS); + } + + private void checkNews() { + try { + String body = WebUtils.getBody(Constants.NEWS_OVERVIEW_URL); + JsonArray array = gson.fromJson(body, JsonArray.class); + + try { + for (JsonElement newsItemElement : array) { + NewsItem newsItem = NewsItem.readItem(newsItemElement.getAsJsonObject()); + if (newsItem != null) { + addNews(newsItem); + } + } + firstCheck = false; + } catch (Exception e) { + if (logger.isDebug()) { + logger.error("Error while reading news item", e); + } + } + } catch (JsonSyntaxException ignored) {} + } + + public void handleNews(GeyserSession session, NewsItemAction action) { + for (NewsItem news : getActiveNews(action)) { + handleNewsItem(session, news, action); + } + } + + private void handleNewsItem(GeyserSession session, NewsItem news, NewsItemAction action) { + switch (action) { + case ON_SERVER_STARTED: + if (!firstCheck) { + return; + } + case BROADCAST_TO_CONSOLE: + logger.info(news.getMessage()); + break; + case ON_OPERATOR_JOIN: + //todo doesn't work, it's called before we know the op level. +// if (session != null && session.getOpPermissionLevel() >= 2) { +// session.sendMessage(ChatColor.GREEN + news.getMessage()); +// } + break; + case BROADCAST_TO_OPERATORS: + for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { + if (player.getOpPermissionLevel() >= 2) { + session.sendMessage(ChatColor.GREEN + news.getMessage()); + } + } + break; + } + } + + public Collection getActiveNews() { + return activeNewsItems.values(); + } + + public Collection getActiveNews(NewsItemAction action) { + List news = new ArrayList<>(); + for (NewsItem item : getActiveNews()) { + if (item.getActions().contains(action)) { + news.add(item); + } + } + return news; + } + + public void addNews(NewsItem item) { + if (activeNewsItems.containsKey(item.getId())) { + if (!item.isActive()) { + activeNewsItems.remove(item.getId()); + } + return; + } + + if (!item.isActive()) { + return; + } + + if (!item.isGlobal() && !Constants.NEWS_PROJECT_NAME.equals(item.getProject())) { + return; + } + + switch (item.getType()) { + case BUILD_SPECIFIC: + if (!item.getDataAs(BuildSpecificData.class).isAffected(branch, build)) { + return; + } + break; + case CHECK_AFTER: + long checkAfter = item.getDataAs(CheckAfterData.class).getCheckAfter(); + long delayMs = System.currentTimeMillis() - checkAfter; + schedule(delayMs > 0 ? delayMs : 0); + break; + } + + activeNewsItems.put(item.getId(), item); + activateNews(item); + } + + private void activateNews(NewsItem item) { + for (NewsItemAction action : item.getActions()) { + handleNewsItem(null, item, action); + } + } + + public void shutdown() { + executorService.shutdown(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index c5a7d9e8dea..a914f699e43 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,35 +25,63 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; +import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; public class PluginMessageUtils { - - private static final byte[] BRAND_DATA; + 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 = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); - byte[] varInt = writeVarInt(data.length); - BRAND_DATA = new byte[varInt.length + data.length]; - System.arraycopy(varInt, 0, BRAND_DATA, 0, varInt.length); - System.arraycopy(data, 0, BRAND_DATA, varInt.length, data.length); + byte[] data = GeyserConnector.NAME.getBytes(Charsets.UTF_8); + GEYSER_BRAND_DATA = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + + FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8); } /** * Get the prebuilt brand as a byte array + * * @return the brand information of the Geyser client */ public static byte[] getGeyserBrandData() { - return BRAND_DATA; + 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 ClientPluginMessagePacket(channel, data)); } private static byte[] writeVarInt(int value) { byte[] data = new byte[getVarIntLength(value)]; int index = 0; do { - byte temp = (byte)(value & 0b01111111); + byte temp = (byte) (value & 0b01111111); value >>>= 7; if (value != 0) { temp |= 0b10000000; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 1d06c8a0f1a..3937fa9a6ed 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -27,66 +27,70 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.DropdownComponent; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.component.ToggleComponent; -import org.geysermc.common.window.response.CustomFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; - -import java.util.ArrayList; +import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.component.DropdownComponent; +import org.geysermc.cumulus.response.CustomFormResponse; public class SettingsUtils { - - // Used in UpstreamPacketHandler.java - public static final int SETTINGS_FORM_ID = 1338; - /** * Build a settings form for the given session and store it for later * * @param session The session to build the form for */ - public static void buildForm(GeyserSession session) { + public static CustomForm buildForm(GeyserSession session) { // Cache the language for cleaner access String language = session.getLocale(); - CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); - builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); + CustomForm.Builder builder = CustomForm.builder() + .translator(SettingsUtils::translateEntry, language) + .title("geyser.settings.title.main") + .iconPath("textures/ui/settings_glyph_color_2x.png"); - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); + // Only show the client title if any of the client settings are available + boolean showClientSettings = session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED; + if (showClientSettings) { + builder.label("geyser.settings.title.client"); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isPrefersShowCoordinates())); - } + // 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()); + } + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + DropdownComponent.Builder cooldownDropdown = DropdownComponent.builder("options.attackIndicator"); + cooldownDropdown.option("options.attack.crosshair", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.TITLE); + cooldownDropdown.option("options.attack.hotbar", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.ACTIONBAR); + cooldownDropdown.option("options.off", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.DISABLED); + builder.dropdown(cooldownDropdown); + } + } - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language))); + boolean canModifyServer = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server"); + if (canModifyServer) { + builder.label("geyser.settings.title.server"); - DropdownComponent gamemodeDropdown = new DropdownComponent(); - gamemodeDropdown.setText("%createWorldScreen.gameMode.personal"); - gamemodeDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal"); for (GameMode gamemode : GameMode.values()) { - gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode); + gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode); } - builder.addComponent(gamemodeDropdown); + builder.dropdown(gamemodeDropdown); - DropdownComponent difficultyDropdown = new DropdownComponent(); - difficultyDropdown.setText("%options.difficulty"); - difficultyDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty"); for (Difficulty difficulty : Difficulty.values()) { - difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); + difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); } - builder.addComponent(difficultyDropdown); + builder.dropdown(difficultyDropdown); } - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language))); + boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules"); + if (showGamerules) { + builder.label("geyser.settings.title.game_rules") + .translator(LocaleUtils::getLocaleString); // we need translate gamerules next + + WorldManager worldManager = GeyserConnector.getInstance().getWorldManager(); for (GameRule gamerule : GameRule.values()) { if (gamerule.equals(GameRule.UNKNOWN)) { continue; @@ -94,81 +98,72 @@ public static void buildForm(GeyserSession session) { // Add the relevant form item based on the gamerule type if (Boolean.class.equals(gamerule.getType())) { - builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule))); + builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule)); } else if (Integer.class.equals(gamerule.getType())) { - builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule)))); + builder.input("gamerule." + gamerule.getJavaID(), "", String.valueOf(worldManager.getGameRuleInt(session, gamerule))); } } } - session.setSettingsForm(builder.build()); - } - - /** - * Handle the settings form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleSettingsForm(GeyserSession session, String response) { - CustomFormWindow settingsForm = session.getSettingsForm(); - settingsForm.setResponse(response); - - CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse(); - if (settingsResponse == null) { - return false; - } - int offset = 0; - - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { - offset++; // Client settings title - - session.getWorldCache().setPrefersShowCoordinates(settingsResponse.getToggleResponses().get(offset)); - session.getWorldCache().updateShowCoordinates(); - offset++; - } - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - offset++; // Server settings title - - GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (gameMode != null && gameMode != session.getGameMode()) { - session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); + builder.responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponse(responseData); + if (response.isClosed() || response.isInvalid()) { + return; } - offset++; - Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { - session.getConnector().getWorldManager().setDifficulty(session, difficulty); + if (showClientSettings) { + // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. + if (session.getPreferencesCache().isAllowShowCoordinates()) { + session.getPreferencesCache().setPrefersShowCoordinates(response.next()); + session.getPreferencesCache().updateShowCoordinates(); + } + + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[(int) response.next()]; + session.getPreferencesCache().setCooldownPreference(cooldownType); + } } - offset++; - } - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - offset++; // Game rule title + if (canModifyServer) { + GameMode gameMode = GameMode.values()[(int) response.next()]; + if (gameMode != null && gameMode != session.getGameMode()) { + session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); + } - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; + Difficulty difficulty = Difficulty.values()[(int) response.next()]; + if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { + session.getConnector().getWorldManager().setDifficulty(session, difficulty); } + } - if (Boolean.class.equals(gamerule.getType())) { - boolean value = settingsResponse.getToggleResponses().get(offset); - if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + if (showGamerules) { + for (GameRule gamerule : GameRule.VALUES) { + if (gamerule.equals(GameRule.UNKNOWN)) { + continue; } - } else if (Integer.class.equals(gamerule.getType())) { - int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset)); - if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + + if (Boolean.class.equals(gamerule.getType())) { + boolean value = response.next(); + if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } + } else if (Integer.class.equals(gamerule.getType())) { + int value = Integer.parseInt(response.next()); + if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } } } - offset++; } - } + }); + + return builder.build(); + } - return true; + private static String translateEntry(String key, String locale) { + if (key.startsWith("geyser.")) { + return LanguageUtils.getPlayerLocaleString(key, locale); + } + return LocaleUtils.getLocaleString(key, locale); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 08521284baa..e59807d7536 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -28,197 +28,171 @@ import com.github.steveice10.mc.protocol.data.MagicValues; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; +import org.geysermc.cumulus.util.FormImage; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class StatisticsUtils { - - // Used in UpstreamPacketHandler.java - public static final int STATISTICS_MENU_FORM_ID = 1339; - public static final int STATISTICS_LIST_FORM_ID = 1340; + private static final Pattern CONTENT_PATTERN = Pattern.compile("^\\S+:", Pattern.MULTILINE); /** * Build a form for the given session with all statistic categories * * @param session The session to build the form for */ - public static SimpleFormWindow buildMenuForm(GeyserSession session) { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); - - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/World"))); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/iron_pickaxe"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/record_11"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/crafting_table_side"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/Wrenches1"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language), new FormImage(FormImage.FormImageType.PATH, "textures/blocks/chest_front"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/trash_default"))); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language), new FormImage(FormImage.FormImageType.PATH, "textures/items/diamond_sword"))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language), new FormImage(FormImage.FormImageType.PATH, "textures/ui/wither_heart_flash"))); - - return window; - } - - /** - * Handle the menu form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleMenuForm(GeyserSession session, String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); - menuForm.setResponse(response); - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - + public static void buildAndSendStatisticsMenu(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); - - if (formResponse != null && formResponse.getClickedButton() != null) { - String title; - StringBuilder content = new StringBuilder(); - - switch (formResponse.getClickedButtonId()) { - case 0: - title = LocaleUtils.getLocaleString("stat.generalButton", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof GenericStatistic) { - content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 1: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakBlockStatistic) { - String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); - block = block.replace("minecraft:", "block.minecraft."); - block = LocaleUtils.getLocaleString(block, language); - content.append(block + ": " + entry.getValue() + "\n"); - } - } - break; - case 2: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 3: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof CraftItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 4: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof UseItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 5: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof PickupItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 6: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof DropItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 7: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KillEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - case 8: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KilledByEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - default: - return false; - } - - if (content.length() == 0) { - content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); - } - - SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language), new FormImage(FormImage.FormImageType.PATH, "textures/gui/newgui/undo"))); - session.sendForm(window, STATISTICS_LIST_FORM_ID); - } - - return true; - } - - /** - * Handle the list form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleListForm(GeyserSession session, String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); - listForm.setResponse(response); - - if (!listForm.isClosed()) { - session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); - } - - return true; + String language = session.getLocale(); + + session.sendForm( + SimpleForm.builder() + .translator(StatisticsUtils::translate, language) + .title("gui.stats") + .button("stat.generalButton", FormImage.Type.PATH, "textures/ui/World") + .button("stat.itemsButton - stat_type.minecraft.mined", FormImage.Type.PATH, "textures/items/iron_pickaxe") + .button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/item/record_11") + .button("stat.itemsButton - stat_type.minecraft.crafted", FormImage.Type.PATH, "textures/blocks/crafting_table_side") + .button("stat.itemsButton - stat_type.minecraft.used", FormImage.Type.PATH, "textures/ui/Wrenches1") + .button("stat.itemsButton - stat_type.minecraft.picked_up", FormImage.Type.PATH, "textures/blocks/chest_front") + .button("stat.itemsButton - stat_type.minecraft.dropped", FormImage.Type.PATH, "textures/ui/trash_default") + .button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamon_sword") + .button("stat.mobsButton - geyser.statistics.killed_by", FormImage.Type.PATH, "textures/ui/wither_heart_flash") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(StatisticsUtils::translate, language); + + StringBuilder content = new StringBuilder(); + + switch (response.getClickedButtonId()) { + case 0: + builder.title("stat.generalButton"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase(); + content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 1: + builder.title("stat.itemsButton - stat_type.minecraft.mined"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + content.append(block).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 2: + builder.title("stat.itemsButton - stat_type.minecraft.broken"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 3: + builder.title("stat.itemsButton - stat_type.minecraft.crafted"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 4: + builder.title("stat.itemsButton - stat_type.minecraft.used"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 5: + builder.title("stat.itemsButton - stat_type.minecraft.picked_up"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 6: + builder.title("stat.itemsButton - stat_type.minecraft.dropped"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 7: + builder.title("stat.mobsButton - geyser.statistics.killed"); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 8: + builder.title("stat.mobsButton - geyser.statistics.killed_by"); + + for (Map.Entry entry : session + .getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + default: + return; + } + + if (content.length() == 0) { + content = new StringBuilder("geyser.statistics.none"); + } + + session.sendForm( + builder.content(content.toString()) + .button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo") + .responseHandler((form1, subFormResponseData) -> { + SimpleFormResponse response1 = form.parseResponse(subFormResponseData); + if (response1.isCorrect()) { + buildAndSendStatisticsMenu(session); + } + })); + })); } /** * Finds the item translation key from the Java locale. - * - * @param item the namespaced item to search for. + * + * @param item the namespaced item to search for. * @param language the language to search in * @return the full name of the item */ @@ -231,4 +205,31 @@ private static String getItemTranslateKey(String item, String language) { } return translatedItem; } + + private static String translate(String keys, String locale) { + Matcher matcher = CONTENT_PATTERN.matcher(keys); + + StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + String group = matcher.group(); + matcher.appendReplacement(buffer, translateEntry(group.substring(0, group.length() - 1), locale) + ":"); + } + + if (buffer.length() != 0) { + return matcher.appendTail(buffer).toString(); + } + + String[] keySplitted = keys.split(" - "); + for (int i = 0; i < keySplitted.length; i++) { + keySplitted[i] = translateEntry(keySplitted[i], locale); + } + return String.join(" - ", keySplitted); + } + + private static String translateEntry(String key, String locale) { + if (key.startsWith("geyser.")) { + return LanguageUtils.getPlayerLocaleString(key, locale); + } + return LocaleUtils.getLocaleString(key, locale); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index ba008da4176..cf9f0e1fb7a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -45,9 +45,8 @@ public class WebUtils { * @return Body contents or error message if the request fails */ public static String getBody(String reqURL) { - URL url = null; try { - url = new URL(reqURL); + URL url = new URL(reqURL); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); // Otherwise Java 8 fails on checking updates diff --git a/connector/src/main/resources/bedrock/biome_definitions.dat b/connector/src/main/resources/bedrock/biome_definitions.dat index 6d72cc92491..8fe3b95a585 100644 Binary files a/connector/src/main/resources/bedrock/biome_definitions.dat and b/connector/src/main/resources/bedrock/biome_definitions.dat differ diff --git a/connector/src/main/resources/bedrock/block_palette.1_17_0.nbt b/connector/src/main/resources/bedrock/block_palette.1_17_0.nbt new file mode 100644 index 00000000000..c4f908b5e7e Binary files /dev/null and b/connector/src/main/resources/bedrock/block_palette.1_17_0.nbt differ diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt deleted file mode 100644 index 4513be031b0..00000000000 Binary files a/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt and /dev/null differ diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt deleted file mode 100644 index 178a370ed09..00000000000 Binary files a/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt and /dev/null differ diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index bc5bded524d..2226e8272c2 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -2,155 +2,171 @@ "items" : [ { "id" : "minecraft:planks", - "blockRuntimeId" : 4812 + "blockRuntimeId" : 5640 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4813 + "blockRuntimeId" : 5641 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4814 + "blockRuntimeId" : 5642 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4815 + "blockRuntimeId" : 5643 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4816 + "blockRuntimeId" : 5644 }, { "id" : "minecraft:planks", - "blockRuntimeId" : 4817 + "blockRuntimeId" : 5645 }, { "id" : "minecraft:crimson_planks", - "blockRuntimeId" : 3484 + "blockRuntimeId" : 3799 }, { "id" : "minecraft:warped_planks", - "blockRuntimeId" : 6315 + "blockRuntimeId" : 7352 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 967 + "blockRuntimeId" : 1278 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 968 + "blockRuntimeId" : 1279 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 969 + "blockRuntimeId" : 1280 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 970 + "blockRuntimeId" : 1281 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 971 + "blockRuntimeId" : 1282 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 972 + "blockRuntimeId" : 1283 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 979 + "blockRuntimeId" : 1290 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 974 + "blockRuntimeId" : 1285 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 975 + "blockRuntimeId" : 1286 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 973 + "blockRuntimeId" : 1284 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 976 + "blockRuntimeId" : 1287 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 980 + "blockRuntimeId" : 1291 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 977 + "blockRuntimeId" : 1288 }, { "id" : "minecraft:cobblestone_wall", - "blockRuntimeId" : 978 + "blockRuntimeId" : 1289 }, { "id" : "minecraft:blackstone_wall", - "blockRuntimeId" : 449 + "blockRuntimeId" : 497 }, { "id" : "minecraft:polished_blackstone_wall", - "blockRuntimeId" : 5046 + "blockRuntimeId" : 5884 }, { "id" : "minecraft:polished_blackstone_brick_wall", - "blockRuntimeId" : 4843 + "blockRuntimeId" : 5681 + }, + { + "id" : "minecraft:cobbled_deepslate_wall", + "blockRuntimeId" : 1115 + }, + { + "id" : "minecraft:deepslate_tile_wall", + "blockRuntimeId" : 4247 + }, + { + "id" : "minecraft:polished_deepslate_wall", + "blockRuntimeId" : 6059 + }, + { + "id" : "minecraft:deepslate_brick_wall", + "blockRuntimeId" : 4064 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4018 + "blockRuntimeId" : 4723 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4019 + "blockRuntimeId" : 4724 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4020 + "blockRuntimeId" : 4725 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4021 + "blockRuntimeId" : 4726 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4022 + "blockRuntimeId" : 4727 }, { "id" : "minecraft:fence", - "blockRuntimeId" : 4023 + "blockRuntimeId" : 4728 }, { "id" : "minecraft:nether_brick_fence", - "blockRuntimeId" : 4738 + "blockRuntimeId" : 5552 }, { "id" : "minecraft:crimson_fence", - "blockRuntimeId" : 3462 + "blockRuntimeId" : 3777 }, { "id" : "minecraft:warped_fence", - "blockRuntimeId" : 6293 + "blockRuntimeId" : 7330 }, { "id" : "minecraft:fence_gate", - "blockRuntimeId" : 4024 + "blockRuntimeId" : 4729 }, { "id" : "minecraft:spruce_fence_gate", - "blockRuntimeId" : 5729 + "blockRuntimeId" : 6764 }, { "id" : "minecraft:birch_fence_gate", - "blockRuntimeId" : 352 + "blockRuntimeId" : 400 }, { "id" : "minecraft:jungle_fence_gate", - "blockRuntimeId" : 4372 + "blockRuntimeId" : 5158 }, { "id" : "minecraft:acacia_fence_gate", @@ -158,43 +174,43 @@ }, { "id" : "minecraft:dark_oak_fence_gate", - "blockRuntimeId" : 3604 + "blockRuntimeId" : 3930 }, { "id" : "minecraft:crimson_fence_gate", - "blockRuntimeId" : 3463 + "blockRuntimeId" : 3778 }, { "id" : "minecraft:warped_fence_gate", - "blockRuntimeId" : 6294 + "blockRuntimeId" : 7331 }, { "id" : "minecraft:normal_stone_stairs", - "blockRuntimeId" : 4757 + "blockRuntimeId" : 5571 }, { "id" : "minecraft:stone_stairs", - "blockRuntimeId" : 6000 + "blockRuntimeId" : 7035 }, { "id" : "minecraft:mossy_cobblestone_stairs", - "blockRuntimeId" : 4719 + "blockRuntimeId" : 5533 }, { "id" : "minecraft:oak_stairs", - "blockRuntimeId" : 4766 + "blockRuntimeId" : 5580 }, { "id" : "minecraft:spruce_stairs", - "blockRuntimeId" : 5761 + "blockRuntimeId" : 6796 }, { "id" : "minecraft:birch_stairs", - "blockRuntimeId" : 384 + "blockRuntimeId" : 432 }, { "id" : "minecraft:jungle_stairs", - "blockRuntimeId" : 4404 + "blockRuntimeId" : 5190 }, { "id" : "minecraft:acacia_stairs", @@ -202,115 +218,163 @@ }, { "id" : "minecraft:dark_oak_stairs", - "blockRuntimeId" : 3636 + "blockRuntimeId" : 3962 }, { "id" : "minecraft:stone_brick_stairs", - "blockRuntimeId" : 5906 + "blockRuntimeId" : 6941 }, { "id" : "minecraft:mossy_stone_brick_stairs", - "blockRuntimeId" : 4727 + "blockRuntimeId" : 5541 }, { "id" : "minecraft:sandstone_stairs", - "blockRuntimeId" : 5516 + "blockRuntimeId" : 6533 }, { "id" : "minecraft:smooth_sandstone_stairs", - "blockRuntimeId" : 5623 + "blockRuntimeId" : 6657 }, { "id" : "minecraft:red_sandstone_stairs", - "blockRuntimeId" : 5443 + "blockRuntimeId" : 6460 }, { "id" : "minecraft:smooth_red_sandstone_stairs", - "blockRuntimeId" : 5615 + "blockRuntimeId" : 6649 }, { "id" : "minecraft:granite_stairs", - "blockRuntimeId" : 4132 + "blockRuntimeId" : 4914 }, { "id" : "minecraft:polished_granite_stairs", - "blockRuntimeId" : 5216 + "blockRuntimeId" : 6229 }, { "id" : "minecraft:diorite_stairs", - "blockRuntimeId" : 3738 + "blockRuntimeId" : 4425 }, { "id" : "minecraft:polished_diorite_stairs", - "blockRuntimeId" : 5208 + "blockRuntimeId" : 6221 }, { "id" : "minecraft:andesite_stairs", - "blockRuntimeId" : 137 + "blockRuntimeId" : 144 }, { "id" : "minecraft:polished_andesite_stairs", - "blockRuntimeId" : 4819 + "blockRuntimeId" : 5657 }, { "id" : "minecraft:brick_stairs", - "blockRuntimeId" : 808 + "blockRuntimeId" : 856 }, { "id" : "minecraft:nether_brick_stairs", - "blockRuntimeId" : 4739 + "blockRuntimeId" : 5553 }, { "id" : "minecraft:red_nether_brick_stairs", - "blockRuntimeId" : 5431 + "blockRuntimeId" : 6448 }, { "id" : "minecraft:end_brick_stairs", - "blockRuntimeId" : 3978 + "blockRuntimeId" : 4669 }, { "id" : "minecraft:quartz_stairs", - "blockRuntimeId" : 5378 + "blockRuntimeId" : 6392 }, { "id" : "minecraft:smooth_quartz_stairs", - "blockRuntimeId" : 5607 + "blockRuntimeId" : 6641 }, { "id" : "minecraft:purpur_stairs", - "blockRuntimeId" : 5356 + "blockRuntimeId" : 6370 }, { "id" : "minecraft:prismarine_stairs", - "blockRuntimeId" : 5278 + "blockRuntimeId" : 6292 }, { "id" : "minecraft:dark_prismarine_stairs", - "blockRuntimeId" : 3660 + "blockRuntimeId" : 3986 }, { "id" : "minecraft:prismarine_bricks_stairs", - "blockRuntimeId" : 5270 + "blockRuntimeId" : 6284 }, { "id" : "minecraft:crimson_stairs", - "blockRuntimeId" : 3504 + "blockRuntimeId" : 3819 }, { "id" : "minecraft:warped_stairs", - "blockRuntimeId" : 6335 + "blockRuntimeId" : 7372 }, { "id" : "minecraft:blackstone_stairs", - "blockRuntimeId" : 441 + "blockRuntimeId" : 489 }, { "id" : "minecraft:polished_blackstone_stairs", - "blockRuntimeId" : 5038 + "blockRuntimeId" : 5876 }, { "id" : "minecraft:polished_blackstone_brick_stairs", - "blockRuntimeId" : 4835 + "blockRuntimeId" : 5673 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 3872 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 4705 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 7499 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 5611 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 7443 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 7457 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 7485 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_stairs", + "blockRuntimeId" : 7471 + }, + { + "id" : "minecraft:cobbled_deepslate_stairs", + "blockRuntimeId" : 1107 + }, + { + "id" : "minecraft:deepslate_tile_stairs", + "blockRuntimeId" : 4239 + }, + { + "id" : "minecraft:polished_deepslate_stairs", + "blockRuntimeId" : 6051 + }, + { + "id" : "minecraft:deepslate_brick_stairs", + "blockRuntimeId" : 4056 }, { "id" : "minecraft:wooden_door" @@ -341,19 +405,19 @@ }, { "id" : "minecraft:trapdoor", - "blockRuntimeId" : 6081 + "blockRuntimeId" : 7117 }, { "id" : "minecraft:spruce_trapdoor", - "blockRuntimeId" : 5785 + "blockRuntimeId" : 6820 }, { "id" : "minecraft:birch_trapdoor", - "blockRuntimeId" : 408 + "blockRuntimeId" : 456 }, { "id" : "minecraft:jungle_trapdoor", - "blockRuntimeId" : 4428 + "blockRuntimeId" : 5214 }, { "id" : "minecraft:acacia_trapdoor", @@ -361,1243 +425,1451 @@ }, { "id" : "minecraft:dark_oak_trapdoor", - "blockRuntimeId" : 3644 + "blockRuntimeId" : 3970 }, { "id" : "minecraft:iron_trapdoor", - "blockRuntimeId" : 4287 + "blockRuntimeId" : 5073 }, { "id" : "minecraft:crimson_trapdoor", - "blockRuntimeId" : 3531 + "blockRuntimeId" : 3846 }, { "id" : "minecraft:warped_trapdoor", - "blockRuntimeId" : 6362 + "blockRuntimeId" : 7399 }, { "id" : "minecraft:iron_bars", - "blockRuntimeId" : 4252 + "blockRuntimeId" : 5038 }, { "id" : "minecraft:glass", - "blockRuntimeId" : 4114 + "blockRuntimeId" : 4820 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5807 + "blockRuntimeId" : 6842 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5815 + "blockRuntimeId" : 6850 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5814 + "blockRuntimeId" : 6849 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5822 + "blockRuntimeId" : 6857 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5819 + "blockRuntimeId" : 6854 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5821 + "blockRuntimeId" : 6856 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5808 + "blockRuntimeId" : 6843 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5811 + "blockRuntimeId" : 6846 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5812 + "blockRuntimeId" : 6847 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5820 + "blockRuntimeId" : 6855 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5816 + "blockRuntimeId" : 6851 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5810 + "blockRuntimeId" : 6845 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5818 + "blockRuntimeId" : 6853 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5817 + "blockRuntimeId" : 6852 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5809 + "blockRuntimeId" : 6844 }, { "id" : "minecraft:stained_glass", - "blockRuntimeId" : 5813 + "blockRuntimeId" : 6848 + }, + { + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 7106 }, { "id" : "minecraft:glass_pane", - "blockRuntimeId" : 4115 + "blockRuntimeId" : 4821 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5823 + "blockRuntimeId" : 6858 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5831 + "blockRuntimeId" : 6866 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5830 + "blockRuntimeId" : 6865 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5838 + "blockRuntimeId" : 6873 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5835 + "blockRuntimeId" : 6870 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5837 + "blockRuntimeId" : 6872 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5824 + "blockRuntimeId" : 6859 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5827 + "blockRuntimeId" : 6862 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5828 + "blockRuntimeId" : 6863 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5836 + "blockRuntimeId" : 6871 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5832 + "blockRuntimeId" : 6867 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5826 + "blockRuntimeId" : 6861 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5834 + "blockRuntimeId" : 6869 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5833 + "blockRuntimeId" : 6868 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5825 + "blockRuntimeId" : 6860 }, { "id" : "minecraft:stained_glass_pane", - "blockRuntimeId" : 5829 + "blockRuntimeId" : 6864 }, { "id" : "minecraft:ladder", - "blockRuntimeId" : 4476 + "blockRuntimeId" : 5262 }, { "id" : "minecraft:scaffolding", - "blockRuntimeId" : 5536 + "blockRuntimeId" : 6553 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5942 + "blockRuntimeId" : 6977 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5992 + "blockRuntimeId" : 7027 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5945 + "blockRuntimeId" : 6980 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5963 + "blockRuntimeId" : 6998 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6540 + "blockRuntimeId" : 7647 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6541 + "blockRuntimeId" : 7648 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6542 + "blockRuntimeId" : 7649 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6543 + "blockRuntimeId" : 7650 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6544 + "blockRuntimeId" : 7651 }, { "id" : "minecraft:wooden_slab", - "blockRuntimeId" : 6545 + "blockRuntimeId" : 7652 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5947 + "blockRuntimeId" : 6982 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5990 + "blockRuntimeId" : 7025 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5943 + "blockRuntimeId" : 6978 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5993 + "blockRuntimeId" : 7028 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5964 + "blockRuntimeId" : 6999 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5958 + "blockRuntimeId" : 6993 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5994 + "blockRuntimeId" : 7029 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5975 + "blockRuntimeId" : 7010 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5980 + "blockRuntimeId" : 7015 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5981 + "blockRuntimeId" : 7016 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5978 + "blockRuntimeId" : 7013 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5979 + "blockRuntimeId" : 7014 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5977 + "blockRuntimeId" : 7012 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5976 + "blockRuntimeId" : 7011 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5946 + "blockRuntimeId" : 6981 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5949 + "blockRuntimeId" : 6984 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5965 + "blockRuntimeId" : 7000 }, { "id" : "minecraft:double_stone_slab3", - "blockRuntimeId" : 5974 + "blockRuntimeId" : 7009 }, { "id" : "minecraft:double_stone_slab", - "blockRuntimeId" : 5948 + "blockRuntimeId" : 6983 }, { "id" : "minecraft:double_stone_slab4", - "blockRuntimeId" : 5991 + "blockRuntimeId" : 7026 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5959 + "blockRuntimeId" : 6994 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5960 + "blockRuntimeId" : 6995 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5961 + "blockRuntimeId" : 6996 }, { "id" : "minecraft:double_stone_slab2", - "blockRuntimeId" : 5962 + "blockRuntimeId" : 6997 }, { "id" : "minecraft:crimson_slab", - "blockRuntimeId" : 3502 + "blockRuntimeId" : 3817 }, { "id" : "minecraft:warped_slab", - "blockRuntimeId" : 6333 + "blockRuntimeId" : 7370 }, { "id" : "minecraft:blackstone_slab", - "blockRuntimeId" : 439 + "blockRuntimeId" : 487 }, { "id" : "minecraft:polished_blackstone_slab", - "blockRuntimeId" : 5036 + "blockRuntimeId" : 5874 }, { "id" : "minecraft:polished_blackstone_brick_slab", - "blockRuntimeId" : 4833 + "blockRuntimeId" : 5671 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 3870 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 4703 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 7497 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 5609 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 7441 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 7455 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 7483 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 7469 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 1105 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 6049 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 4237 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 4054 }, { "id" : "minecraft:brick_block", - "blockRuntimeId" : 807 + "blockRuntimeId" : 855 }, { "id" : "minecraft:chiseled_nether_bricks", - "blockRuntimeId" : 954 + "blockRuntimeId" : 1090 }, { "id" : "minecraft:cracked_nether_bricks", - "blockRuntimeId" : 3413 + "blockRuntimeId" : 3728 }, { "id" : "minecraft:quartz_bricks", - "blockRuntimeId" : 5376 + "blockRuntimeId" : 6390 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6008 + "blockRuntimeId" : 7043 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6009 + "blockRuntimeId" : 7044 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6010 + "blockRuntimeId" : 7045 }, { "id" : "minecraft:stonebrick", - "blockRuntimeId" : 6011 + "blockRuntimeId" : 7046 }, { "id" : "minecraft:end_bricks", - "blockRuntimeId" : 3986 + "blockRuntimeId" : 4677 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 5269 + "blockRuntimeId" : 6283 }, { "id" : "minecraft:polished_blackstone_bricks", - "blockRuntimeId" : 5005 + "blockRuntimeId" : 5843 }, { "id" : "minecraft:cracked_polished_blackstone_bricks", - "blockRuntimeId" : 3414 + "blockRuntimeId" : 3729 }, { "id" : "minecraft:gilded_blackstone", - "blockRuntimeId" : 4113 + "blockRuntimeId" : 4819 }, { "id" : "minecraft:chiseled_polished_blackstone", - "blockRuntimeId" : 955 + "blockRuntimeId" : 1091 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 4409 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 3727 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 4226 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 3726 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 1089 }, { "id" : "minecraft:cobblestone", - "blockRuntimeId" : 966 + "blockRuntimeId" : 1277 }, { "id" : "minecraft:mossy_cobblestone", - "blockRuntimeId" : 4718 + "blockRuntimeId" : 5532 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 1102 }, { "id" : "minecraft:smooth_stone", - "blockRuntimeId" : 5631 + "blockRuntimeId" : 6665 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 5512 + "blockRuntimeId" : 6529 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 5513 + "blockRuntimeId" : 6530 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 5514 + "blockRuntimeId" : 6531 }, { "id" : "minecraft:sandstone", - "blockRuntimeId" : 5515 + "blockRuntimeId" : 6532 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5439 + "blockRuntimeId" : 6456 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5440 + "blockRuntimeId" : 6457 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5441 + "blockRuntimeId" : 6458 }, { "id" : "minecraft:red_sandstone", - "blockRuntimeId" : 5442 + "blockRuntimeId" : 6459 }, { "id" : "minecraft:coal_block", - "blockRuntimeId" : 964 + "blockRuntimeId" : 1100 }, { "id" : "minecraft:dried_kelp_block", - "blockRuntimeId" : 3843 + "blockRuntimeId" : 4533 }, { "id" : "minecraft:gold_block", - "blockRuntimeId" : 4118 + "blockRuntimeId" : 4900 }, { "id" : "minecraft:iron_block", - "blockRuntimeId" : 4253 + "blockRuntimeId" : 5039 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 3636 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 4701 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 7495 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 5607 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 7439 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 7453 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 7481 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 7467 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 3869 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 4702 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 7496 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 5608 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 7440 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 7454 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 7482 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 7468 }, { "id" : "minecraft:emerald_block", - "blockRuntimeId" : 3975 + "blockRuntimeId" : 4666 }, { "id" : "minecraft:diamond_block", - "blockRuntimeId" : 3736 + "blockRuntimeId" : 4423 }, { "id" : "minecraft:lapis_block", - "blockRuntimeId" : 4484 + "blockRuntimeId" : 5270 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 6412 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 6410 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 6411 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5364 + "blockRuntimeId" : 6378 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5366 + "blockRuntimeId" : 6380 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5365 + "blockRuntimeId" : 6379 }, { "id" : "minecraft:quartz_block", - "blockRuntimeId" : 5367 + "blockRuntimeId" : 6381 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 5267 + "blockRuntimeId" : 6281 }, { "id" : "minecraft:prismarine", - "blockRuntimeId" : 5268 + "blockRuntimeId" : 6282 }, { "id" : "minecraft:slime", - "blockRuntimeId" : 5599 + "blockRuntimeId" : 6618 }, { "id" : "minecraft:honey_block", - "blockRuntimeId" : 4234 + "blockRuntimeId" : 5017 }, { "id" : "minecraft:honeycomb_block", - "blockRuntimeId" : 4235 + "blockRuntimeId" : 5018 }, { "id" : "minecraft:hay_block", - "blockRuntimeId" : 4206 + "blockRuntimeId" : 4989 }, { "id" : "minecraft:bone_block", - "blockRuntimeId" : 624 + "blockRuntimeId" : 672 }, { "id" : "minecraft:nether_brick", - "blockRuntimeId" : 4737 + "blockRuntimeId" : 5551 }, { "id" : "minecraft:red_nether_brick", - "blockRuntimeId" : 5430 + "blockRuntimeId" : 6447 }, { "id" : "minecraft:netherite_block", - "blockRuntimeId" : 4754 + "blockRuntimeId" : 5568 }, { "id" : "minecraft:lodestone", - "blockRuntimeId" : 4632 + "blockRuntimeId" : 5438 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6552 + "blockRuntimeId" : 7659 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6560 + "blockRuntimeId" : 7667 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6559 + "blockRuntimeId" : 7666 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6567 + "blockRuntimeId" : 7674 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6564 + "blockRuntimeId" : 7671 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6566 + "blockRuntimeId" : 7673 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6553 + "blockRuntimeId" : 7660 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6556 + "blockRuntimeId" : 7663 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6557 + "blockRuntimeId" : 7664 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6565 + "blockRuntimeId" : 7672 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6561 + "blockRuntimeId" : 7668 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6555 + "blockRuntimeId" : 7662 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6563 + "blockRuntimeId" : 7670 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6562 + "blockRuntimeId" : 7669 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6554 + "blockRuntimeId" : 7661 }, { "id" : "minecraft:wool", - "blockRuntimeId" : 6558 + "blockRuntimeId" : 7665 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 873 + "blockRuntimeId" : 923 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 881 + "blockRuntimeId" : 931 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 880 + "blockRuntimeId" : 930 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 888 + "blockRuntimeId" : 938 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 885 + "blockRuntimeId" : 935 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 887 + "blockRuntimeId" : 937 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 874 + "blockRuntimeId" : 924 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 877 + "blockRuntimeId" : 927 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 878 + "blockRuntimeId" : 928 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 886 + "blockRuntimeId" : 936 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 882 + "blockRuntimeId" : 932 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 876 + "blockRuntimeId" : 926 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 884 + "blockRuntimeId" : 934 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 883 + "blockRuntimeId" : 933 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 875 + "blockRuntimeId" : 925 }, { "id" : "minecraft:carpet", - "blockRuntimeId" : 879 + "blockRuntimeId" : 929 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3308 + "blockRuntimeId" : 3619 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3316 + "blockRuntimeId" : 3627 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3315 + "blockRuntimeId" : 3626 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3323 + "blockRuntimeId" : 3634 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3320 + "blockRuntimeId" : 3631 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3322 + "blockRuntimeId" : 3633 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3309 + "blockRuntimeId" : 3620 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3312 + "blockRuntimeId" : 3623 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3313 + "blockRuntimeId" : 3624 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3321 + "blockRuntimeId" : 3632 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3317 + "blockRuntimeId" : 3628 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3311 + "blockRuntimeId" : 3622 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3319 + "blockRuntimeId" : 3630 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3318 + "blockRuntimeId" : 3629 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3310 + "blockRuntimeId" : 3621 }, { "id" : "minecraft:concrete_powder", - "blockRuntimeId" : 3314 + "blockRuntimeId" : 3625 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3292 + "blockRuntimeId" : 3603 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3300 + "blockRuntimeId" : 3611 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3299 + "blockRuntimeId" : 3610 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3307 + "blockRuntimeId" : 3618 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3304 + "blockRuntimeId" : 3615 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3306 + "blockRuntimeId" : 3617 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3293 + "blockRuntimeId" : 3604 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3296 + "blockRuntimeId" : 3607 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3297 + "blockRuntimeId" : 3608 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3305 + "blockRuntimeId" : 3616 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3301 + "blockRuntimeId" : 3612 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3295 + "blockRuntimeId" : 3606 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3303 + "blockRuntimeId" : 3614 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3302 + "blockRuntimeId" : 3613 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3294 + "blockRuntimeId" : 3605 }, { "id" : "minecraft:concrete", - "blockRuntimeId" : 3298 + "blockRuntimeId" : 3609 }, { "id" : "minecraft:clay", - "blockRuntimeId" : 963 + "blockRuntimeId" : 1099 }, { "id" : "minecraft:hardened_clay", - "blockRuntimeId" : 4205 + "blockRuntimeId" : 4988 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5839 + "blockRuntimeId" : 6874 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5847 + "blockRuntimeId" : 6882 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5846 + "blockRuntimeId" : 6881 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5854 + "blockRuntimeId" : 6889 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5851 + "blockRuntimeId" : 6886 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5853 + "blockRuntimeId" : 6888 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5840 + "blockRuntimeId" : 6875 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5843 + "blockRuntimeId" : 6878 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5844 + "blockRuntimeId" : 6879 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5852 + "blockRuntimeId" : 6887 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5848 + "blockRuntimeId" : 6883 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5842 + "blockRuntimeId" : 6877 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5850 + "blockRuntimeId" : 6885 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5849 + "blockRuntimeId" : 6884 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5841 + "blockRuntimeId" : 6876 }, { "id" : "minecraft:stained_hardened_clay", - "blockRuntimeId" : 5845 + "blockRuntimeId" : 6880 }, { "id" : "minecraft:white_glazed_terracotta", - "blockRuntimeId" : 6437 + "blockRuntimeId" : 7544 }, { "id" : "minecraft:silver_glazed_terracotta", - "blockRuntimeId" : 5581 + "blockRuntimeId" : 6600 }, { "id" : "minecraft:gray_glazed_terracotta", - "blockRuntimeId" : 4143 + "blockRuntimeId" : 4925 }, { "id" : "minecraft:black_glazed_terracotta", - "blockRuntimeId" : 430 + "blockRuntimeId" : 478 }, { "id" : "minecraft:brown_glazed_terracotta", - "blockRuntimeId" : 816 + "blockRuntimeId" : 864 }, { "id" : "minecraft:red_glazed_terracotta", - "blockRuntimeId" : 5407 + "blockRuntimeId" : 6424 }, { "id" : "minecraft:orange_glazed_terracotta", - "blockRuntimeId" : 4787 + "blockRuntimeId" : 5601 }, { "id" : "minecraft:yellow_glazed_terracotta", - "blockRuntimeId" : 6569 + "blockRuntimeId" : 7676 }, { "id" : "minecraft:lime_glazed_terracotta", - "blockRuntimeId" : 4602 + "blockRuntimeId" : 5407 }, { "id" : "minecraft:green_glazed_terracotta", - "blockRuntimeId" : 4149 + "blockRuntimeId" : 4931 }, { "id" : "minecraft:cyan_glazed_terracotta", - "blockRuntimeId" : 3554 + "blockRuntimeId" : 3880 }, { "id" : "minecraft:light_blue_glazed_terracotta", - "blockRuntimeId" : 4580 + "blockRuntimeId" : 5379 }, { "id" : "minecraft:blue_glazed_terracotta", - "blockRuntimeId" : 617 + "blockRuntimeId" : 665 }, { "id" : "minecraft:purple_glazed_terracotta", - "blockRuntimeId" : 5338 + "blockRuntimeId" : 6352 }, { "id" : "minecraft:magenta_glazed_terracotta", - "blockRuntimeId" : 4655 + "blockRuntimeId" : 5461 }, { "id" : "minecraft:pink_glazed_terracotta", - "blockRuntimeId" : 4794 + "blockRuntimeId" : 5622 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 5344 + "blockRuntimeId" : 6358 }, { "id" : "minecraft:purpur_block", - "blockRuntimeId" : 5346 + "blockRuntimeId" : 6360 }, { "id" : "minecraft:nether_wart_block", - "blockRuntimeId" : 4753 + "blockRuntimeId" : 5567 }, { "id" : "minecraft:warped_wart_block", - "blockRuntimeId" : 6384 + "blockRuntimeId" : 7421 }, { "id" : "minecraft:shroomlight", - "blockRuntimeId" : 5564 + "blockRuntimeId" : 6583 }, { "id" : "minecraft:crimson_nylium", - "blockRuntimeId" : 3483 + "blockRuntimeId" : 3798 }, { "id" : "minecraft:warped_nylium", - "blockRuntimeId" : 6314 + "blockRuntimeId" : 7351 }, { "id" : "minecraft:basalt", - "blockRuntimeId" : 198 + "blockRuntimeId" : 214 }, { "id" : "minecraft:polished_basalt", - "blockRuntimeId" : 4827 + "blockRuntimeId" : 5665 + }, + { + "id" : "minecraft:smooth_basalt", + "blockRuntimeId" : 6640 }, { "id" : "minecraft:soul_soil", - "blockRuntimeId" : 5676 + "blockRuntimeId" : 6710 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 3746 + "blockRuntimeId" : 4433 }, { "id" : "minecraft:dirt", - "blockRuntimeId" : 3747 + "blockRuntimeId" : 4434 }, { "id" : "minecraft:farmland", - "blockRuntimeId" : 4010 + "blockRuntimeId" : 4715 }, { "id" : "minecraft:grass", - "blockRuntimeId" : 4140 + "blockRuntimeId" : 4922 }, { "id" : "minecraft:grass_path", - "blockRuntimeId" : 4141 + "blockRuntimeId" : 4923 }, { "id" : "minecraft:podzol", - "blockRuntimeId" : 4818 + "blockRuntimeId" : 5646 }, { "id" : "minecraft:mycelium", - "blockRuntimeId" : 4736 + "blockRuntimeId" : 5550 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5899 + "blockRuntimeId" : 6934 }, { "id" : "minecraft:iron_ore", - "blockRuntimeId" : 4286 + "blockRuntimeId" : 5072 }, { "id" : "minecraft:gold_ore", - "blockRuntimeId" : 4119 + "blockRuntimeId" : 4901 }, { "id" : "minecraft:diamond_ore", - "blockRuntimeId" : 3737 + "blockRuntimeId" : 4424 }, { "id" : "minecraft:lapis_ore", - "blockRuntimeId" : 4485 + "blockRuntimeId" : 5271 }, { "id" : "minecraft:redstone_ore", - "blockRuntimeId" : 5453 + "blockRuntimeId" : 6470 }, { "id" : "minecraft:coal_ore", - "blockRuntimeId" : 965 + "blockRuntimeId" : 1101 }, { "id" : "minecraft:emerald_ore", - "blockRuntimeId" : 3976 + "blockRuntimeId" : 4667 }, { "id" : "minecraft:quartz_ore", - "blockRuntimeId" : 5377 + "blockRuntimeId" : 6391 }, { "id" : "minecraft:nether_gold_ore", - "blockRuntimeId" : 4747 + "blockRuntimeId" : 5561 }, { "id" : "minecraft:ancient_debris", - "blockRuntimeId" : 136 + "blockRuntimeId" : 143 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 3637 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 4232 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 4231 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 4229 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 4233 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 4234 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 4230 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 4227 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 4228 }, { "id" : "minecraft:gravel", - "blockRuntimeId" : 4142 + "blockRuntimeId" : 4924 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5900 + "blockRuntimeId" : 6935 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5902 + "blockRuntimeId" : 6937 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5904 + "blockRuntimeId" : 6939 }, { "id" : "minecraft:blackstone", - "blockRuntimeId" : 436 + "blockRuntimeId" : 484 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5901 + "blockRuntimeId" : 6936 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5903 + "blockRuntimeId" : 6938 }, { "id" : "minecraft:stone", - "blockRuntimeId" : 5905 + "blockRuntimeId" : 6940 }, { "id" : "minecraft:polished_blackstone", - "blockRuntimeId" : 4830 + "blockRuntimeId" : 5668 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 4049 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 6046 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 5510 + "blockRuntimeId" : 6527 }, { "id" : "minecraft:sand", - "blockRuntimeId" : 5511 + "blockRuntimeId" : 6528 }, { "id" : "minecraft:cactus", - "blockRuntimeId" : 841 + "blockRuntimeId" : 890 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4633 + "blockRuntimeId" : 5439 }, { "id" : "minecraft:stripped_oak_log", - "blockRuntimeId" : 6038 + "blockRuntimeId" : 7073 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4634 + "blockRuntimeId" : 5440 }, { "id" : "minecraft:stripped_spruce_log", - "blockRuntimeId" : 6041 + "blockRuntimeId" : 7076 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4635 + "blockRuntimeId" : 5441 }, { "id" : "minecraft:stripped_birch_log", - "blockRuntimeId" : 6023 + "blockRuntimeId" : 7058 }, { "id" : "minecraft:log", - "blockRuntimeId" : 4636 + "blockRuntimeId" : 5442 }, { "id" : "minecraft:stripped_jungle_log", - "blockRuntimeId" : 6035 + "blockRuntimeId" : 7070 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 4645 + "blockRuntimeId" : 5451 }, { "id" : "minecraft:stripped_acacia_log", - "blockRuntimeId" : 6020 + "blockRuntimeId" : 7055 }, { "id" : "minecraft:log2", - "blockRuntimeId" : 4646 + "blockRuntimeId" : 5452 }, { "id" : "minecraft:stripped_dark_oak_log", - "blockRuntimeId" : 6032 + "blockRuntimeId" : 7067 }, { "id" : "minecraft:crimson_stem", - "blockRuntimeId" : 3528 + "blockRuntimeId" : 3843 }, { "id" : "minecraft:stripped_crimson_stem", - "blockRuntimeId" : 6029 + "blockRuntimeId" : 7064 }, { "id" : "minecraft:warped_stem", - "blockRuntimeId" : 6359 + "blockRuntimeId" : 7396 }, { "id" : "minecraft:stripped_warped_stem", - "blockRuntimeId" : 6047 + "blockRuntimeId" : 7082 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6444 + "blockRuntimeId" : 7551 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6450 + "blockRuntimeId" : 7557 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6445 + "blockRuntimeId" : 7552 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6451 + "blockRuntimeId" : 7558 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6446 + "blockRuntimeId" : 7553 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6452 + "blockRuntimeId" : 7559 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6447 + "blockRuntimeId" : 7554 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6453 + "blockRuntimeId" : 7560 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6448 + "blockRuntimeId" : 7555 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6454 + "blockRuntimeId" : 7561 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6449 + "blockRuntimeId" : 7556 }, { "id" : "minecraft:wood", - "blockRuntimeId" : 6455 + "blockRuntimeId" : 7562 }, { "id" : "minecraft:crimson_hyphae", - "blockRuntimeId" : 3480 + "blockRuntimeId" : 3795 }, { "id" : "minecraft:stripped_crimson_hyphae", - "blockRuntimeId" : 6026 + "blockRuntimeId" : 7061 }, { "id" : "minecraft:warped_hyphae", - "blockRuntimeId" : 6311 + "blockRuntimeId" : 7348 }, { "id" : "minecraft:stripped_warped_hyphae", - "blockRuntimeId" : 6044 + "blockRuntimeId" : 7079 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4516 + "blockRuntimeId" : 5315 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4517 + "blockRuntimeId" : 5316 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4518 + "blockRuntimeId" : 5317 }, { "id" : "minecraft:leaves", - "blockRuntimeId" : 4519 + "blockRuntimeId" : 5318 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4532 + "blockRuntimeId" : 5331 }, { "id" : "minecraft:leaves2", - "blockRuntimeId" : 4533 + "blockRuntimeId" : 5332 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 169 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 173 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5524 + "blockRuntimeId" : 6541 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5525 + "blockRuntimeId" : 6542 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5526 + "blockRuntimeId" : 6543 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5527 + "blockRuntimeId" : 6544 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5528 + "blockRuntimeId" : 6545 }, { "id" : "minecraft:sapling", - "blockRuntimeId" : 5529 + "blockRuntimeId" : 6546 }, { "id" : "minecraft:bee_nest", - "blockRuntimeId" : 220 + "blockRuntimeId" : 236 }, { "id" : "minecraft:wheat_seeds" @@ -1640,7 +1912,7 @@ }, { "id" : "minecraft:melon_block", - "blockRuntimeId" : 4662 + "blockRuntimeId" : 5474 }, { "id" : "minecraft:melon_slice" @@ -1651,202 +1923,205 @@ { "id" : "minecraft:sweet_berries" }, + { + "id" : "minecraft:glow_berries" + }, { "id" : "minecraft:pumpkin", - "blockRuntimeId" : 5286 + "blockRuntimeId" : 6300 }, { "id" : "minecraft:carved_pumpkin", - "blockRuntimeId" : 898 + "blockRuntimeId" : 948 }, { "id" : "minecraft:lit_pumpkin", - "blockRuntimeId" : 4620 + "blockRuntimeId" : 5426 }, { "id" : "minecraft:honeycomb" }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 6068 + "blockRuntimeId" : 7103 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3763 + "blockRuntimeId" : 4453 }, { "id" : "minecraft:tallgrass", - "blockRuntimeId" : 6067 + "blockRuntimeId" : 7102 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3762 + "blockRuntimeId" : 4452 }, { "id" : "minecraft:nether_sprouts" }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3328 + "blockRuntimeId" : 3641 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3326 + "blockRuntimeId" : 3639 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3327 + "blockRuntimeId" : 3640 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3325 + "blockRuntimeId" : 3638 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3329 + "blockRuntimeId" : 3642 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3333 + "blockRuntimeId" : 3646 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3331 + "blockRuntimeId" : 3644 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3332 + "blockRuntimeId" : 3645 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3330 + "blockRuntimeId" : 3643 }, { "id" : "minecraft:coral", - "blockRuntimeId" : 3334 + "blockRuntimeId" : 3647 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3348 + "blockRuntimeId" : 3661 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3346 + "blockRuntimeId" : 3659 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3347 + "blockRuntimeId" : 3660 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3345 + "blockRuntimeId" : 3658 }, { "id" : "minecraft:coral_fan", - "blockRuntimeId" : 3349 + "blockRuntimeId" : 3662 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3358 + "blockRuntimeId" : 3671 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3356 + "blockRuntimeId" : 3669 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3357 + "blockRuntimeId" : 3670 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3355 + "blockRuntimeId" : 3668 }, { "id" : "minecraft:coral_fan_dead", - "blockRuntimeId" : 3359 + "blockRuntimeId" : 3672 }, { "id" : "minecraft:kelp" }, { "id" : "minecraft:seagrass", - "blockRuntimeId" : 5560 + "blockRuntimeId" : 6579 }, { "id" : "minecraft:crimson_roots", - "blockRuntimeId" : 3501 + "blockRuntimeId" : 3816 }, { "id" : "minecraft:warped_roots", - "blockRuntimeId" : 6332 + "blockRuntimeId" : 7369 }, { "id" : "minecraft:yellow_flower", - "blockRuntimeId" : 6568 + "blockRuntimeId" : 7675 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5396 + "blockRuntimeId" : 6413 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5397 + "blockRuntimeId" : 6414 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5398 + "blockRuntimeId" : 6415 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5399 + "blockRuntimeId" : 6416 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5400 + "blockRuntimeId" : 6417 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5401 + "blockRuntimeId" : 6418 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5402 + "blockRuntimeId" : 6419 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5403 + "blockRuntimeId" : 6420 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5404 + "blockRuntimeId" : 6421 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5405 + "blockRuntimeId" : 6422 }, { "id" : "minecraft:red_flower", - "blockRuntimeId" : 5406 + "blockRuntimeId" : 6423 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3760 + "blockRuntimeId" : 4450 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3761 + "blockRuntimeId" : 4451 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3764 + "blockRuntimeId" : 4454 }, { "id" : "minecraft:double_plant", - "blockRuntimeId" : 3765 + "blockRuntimeId" : 4455 }, { "id" : "minecraft:wither_rose", - "blockRuntimeId" : 6443 + "blockRuntimeId" : 7550 }, { "id" : "minecraft:white_dye" @@ -1899,6 +2174,9 @@ { "id" : "minecraft:ink_sac" }, + { + "id" : "minecraft:glow_ink_sac" + }, { "id" : "minecraft:cocoa_beans" }, @@ -1910,47 +2188,127 @@ }, { "id" : "minecraft:vine", - "blockRuntimeId" : 6219 + "blockRuntimeId" : 7256 }, { "id" : "minecraft:weeping_vines", - "blockRuntimeId" : 6403 + "blockRuntimeId" : 7510 }, { "id" : "minecraft:twisting_vines", - "blockRuntimeId" : 6147 + "blockRuntimeId" : 7184 }, { "id" : "minecraft:waterlily", - "blockRuntimeId" : 6401 + "blockRuntimeId" : 7438 }, { "id" : "minecraft:deadbush", - "blockRuntimeId" : 3722 + "blockRuntimeId" : 4048 }, { "id" : "minecraft:bamboo", - "blockRuntimeId" : 161 + "blockRuntimeId" : 177 }, { "id" : "minecraft:snow", - "blockRuntimeId" : 5632 + "blockRuntimeId" : 6666 }, { "id" : "minecraft:ice", - "blockRuntimeId" : 4248 + "blockRuntimeId" : 5031 }, { "id" : "minecraft:packed_ice", - "blockRuntimeId" : 4793 + "blockRuntimeId" : 5621 }, { "id" : "minecraft:blue_ice", - "blockRuntimeId" : 623 + "blockRuntimeId" : 671 }, { "id" : "minecraft:snow_layer", - "blockRuntimeId" : 5633 + "blockRuntimeId" : 6667 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 5652 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 4534 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 5531 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 5530 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 4435 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 4953 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 328 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 6632 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 6719 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 168 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 4764 + }, + { + "id" : "minecraft:glow_lichen", + "blockRuntimeId" : 4897 + }, + { + "id" : "minecraft:amethyst_block", + "blockRuntimeId" : 136 + }, + { + "id" : "minecraft:budding_amethyst", + "blockRuntimeId" : 889 + }, + { + "id" : "minecraft:amethyst_cluster", + "blockRuntimeId" : 137 + }, + { + "id" : "minecraft:large_amethyst_bud", + "blockRuntimeId" : 5272 + }, + { + "id" : "minecraft:medium_amethyst_bud", + "blockRuntimeId" : 5468 + }, + { + "id" : "minecraft:small_amethyst_bud", + "blockRuntimeId" : 6619 + }, + { + "id" : "minecraft:tuff", + "blockRuntimeId" : 7171 + }, + { + "id" : "minecraft:calcite", + "blockRuntimeId" : 913 }, { "id" : "minecraft:chicken" @@ -1981,35 +2339,35 @@ }, { "id" : "minecraft:brown_mushroom", - "blockRuntimeId" : 822 + "blockRuntimeId" : 870 }, { "id" : "minecraft:red_mushroom", - "blockRuntimeId" : 5413 + "blockRuntimeId" : 6430 }, { "id" : "minecraft:crimson_fungus", - "blockRuntimeId" : 3479 + "blockRuntimeId" : 3794 }, { "id" : "minecraft:warped_fungus", - "blockRuntimeId" : 6310 + "blockRuntimeId" : 7347 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 837 + "blockRuntimeId" : 885 }, { "id" : "minecraft:red_mushroom_block", - "blockRuntimeId" : 5428 + "blockRuntimeId" : 6445 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 838 + "blockRuntimeId" : 886 }, { "id" : "minecraft:brown_mushroom_block", - "blockRuntimeId" : 823 + "blockRuntimeId" : 871 }, { "id" : "minecraft:egg" @@ -2028,46 +2386,50 @@ }, { "id" : "minecraft:web", - "blockRuntimeId" : 6402 + "blockRuntimeId" : 7509 }, { "id" : "minecraft:spider_eye" }, { "id" : "minecraft:mob_spawner", - "blockRuntimeId" : 4711 + "blockRuntimeId" : 5523 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4712 + "blockRuntimeId" : 5524 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4713 + "blockRuntimeId" : 5525 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4714 + "blockRuntimeId" : 5526 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4715 + "blockRuntimeId" : 5527 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4716 + "blockRuntimeId" : 5528 }, { "id" : "minecraft:monster_egg", - "blockRuntimeId" : 4717 + "blockRuntimeId" : 5529 + }, + { + "id" : "minecraft:infested_deepslate", + "blockRuntimeId" : 5032 }, { "id" : "minecraft:dragon_egg", - "blockRuntimeId" : 3842 + "blockRuntimeId" : 4532 }, { "id" : "minecraft:turtle_egg", - "blockRuntimeId" : 6135 + "blockRuntimeId" : 7172 }, { "id" : "minecraft:chicken_spawn_egg" @@ -2189,6 +2551,9 @@ { "id" : "minecraft:squid_spawn_egg" }, + { + "id" : "minecraft:glow_squid_spawn_egg" + }, { "id" : "minecraft:cave_spider_spawn_egg" }, @@ -2222,6 +2587,12 @@ { "id" : "minecraft:piglin_brute_spawn_egg" }, + { + "id" : "minecraft:goat_spawn_egg" + }, + { + "id" : "minecraft:axolotl_spawn_egg" + }, { "id" : "minecraft:ghast_spawn_egg" }, @@ -2260,42 +2631,42 @@ }, { "id" : "minecraft:obsidian", - "blockRuntimeId" : 4786 + "blockRuntimeId" : 5600 }, { "id" : "minecraft:crying_obsidian", - "blockRuntimeId" : 3553 + "blockRuntimeId" : 3868 }, { "id" : "minecraft:bedrock", - "blockRuntimeId" : 218 + "blockRuntimeId" : 234 }, { "id" : "minecraft:soul_sand", - "blockRuntimeId" : 5675 + "blockRuntimeId" : 6709 }, { "id" : "minecraft:netherrack", - "blockRuntimeId" : 4755 + "blockRuntimeId" : 5569 }, { "id" : "minecraft:magma", - "blockRuntimeId" : 4661 + "blockRuntimeId" : 5467 }, { "id" : "minecraft:nether_wart" }, { "id" : "minecraft:end_stone", - "blockRuntimeId" : 4003 + "blockRuntimeId" : 4694 }, { "id" : "minecraft:chorus_flower", - "blockRuntimeId" : 956 + "blockRuntimeId" : 1092 }, { "id" : "minecraft:chorus_plant", - "blockRuntimeId" : 962 + "blockRuntimeId" : 1098 }, { "id" : "minecraft:chorus_fruit" @@ -2305,51 +2676,51 @@ }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 5683 + "blockRuntimeId" : 6717 }, { "id" : "minecraft:sponge", - "blockRuntimeId" : 5684 + "blockRuntimeId" : 6718 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3335 + "blockRuntimeId" : 3648 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3336 + "blockRuntimeId" : 3649 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3337 + "blockRuntimeId" : 3650 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3338 + "blockRuntimeId" : 3651 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3339 + "blockRuntimeId" : 3652 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3340 + "blockRuntimeId" : 3653 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3341 + "blockRuntimeId" : 3654 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3342 + "blockRuntimeId" : 3655 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3343 + "blockRuntimeId" : 3656 }, { "id" : "minecraft:coral_block", - "blockRuntimeId" : 3344 + "blockRuntimeId" : 3657 }, { "id" : "minecraft:leather_helmet" @@ -3305,6 +3676,9 @@ "id" : "minecraft:lingering_potion", "damage" : 42 }, + { + "id" : "minecraft:spyglass" + }, { "id" : "minecraft:stick" }, @@ -3373,43 +3747,43 @@ }, { "id" : "minecraft:torch", - "blockRuntimeId" : 6075 + "blockRuntimeId" : 7111 }, { "id" : "minecraft:soul_torch", - "blockRuntimeId" : 5677 + "blockRuntimeId" : 6711 }, { "id" : "minecraft:sea_pickle", - "blockRuntimeId" : 5552 + "blockRuntimeId" : 6571 }, { "id" : "minecraft:lantern", - "blockRuntimeId" : 4482 + "blockRuntimeId" : 5268 }, { "id" : "minecraft:soul_lantern", - "blockRuntimeId" : 5673 + "blockRuntimeId" : 6707 }, { "id" : "minecraft:crafting_table", - "blockRuntimeId" : 3415 + "blockRuntimeId" : 3730 }, { "id" : "minecraft:cartography_table", - "blockRuntimeId" : 897 + "blockRuntimeId" : 947 }, { "id" : "minecraft:fletching_table", - "blockRuntimeId" : 4056 + "blockRuntimeId" : 4761 }, { "id" : "minecraft:smithing_table", - "blockRuntimeId" : 5600 + "blockRuntimeId" : 6633 }, { "id" : "minecraft:beehive", - "blockRuntimeId" : 244 + "blockRuntimeId" : 260 }, { "id" : "minecraft:campfire" @@ -3419,152 +3793,152 @@ }, { "id" : "minecraft:furnace", - "blockRuntimeId" : 4107 + "blockRuntimeId" : 4813 }, { "id" : "minecraft:blast_furnace", - "blockRuntimeId" : 611 + "blockRuntimeId" : 659 }, { "id" : "minecraft:smoker", - "blockRuntimeId" : 5601 + "blockRuntimeId" : 6634 }, { "id" : "minecraft:respawn_anchor", - "blockRuntimeId" : 5505 + "blockRuntimeId" : 6522 }, { "id" : "minecraft:brewing_stand" }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 145 + "blockRuntimeId" : 152 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 149 + "blockRuntimeId" : 156 }, { "id" : "minecraft:anvil", - "blockRuntimeId" : 153 + "blockRuntimeId" : 160 }, { "id" : "minecraft:grindstone", - "blockRuntimeId" : 4155 + "blockRuntimeId" : 4937 }, { "id" : "minecraft:enchanting_table", - "blockRuntimeId" : 3977 + "blockRuntimeId" : 4668 }, { "id" : "minecraft:bookshelf", - "blockRuntimeId" : 636 + "blockRuntimeId" : 684 }, { "id" : "minecraft:lectern", - "blockRuntimeId" : 4540 + "blockRuntimeId" : 5339 }, { "id" : "minecraft:cauldron" }, { "id" : "minecraft:composter", - "blockRuntimeId" : 3283 + "blockRuntimeId" : 3594 }, { "id" : "minecraft:chest", - "blockRuntimeId" : 948 + "blockRuntimeId" : 1083 }, { "id" : "minecraft:trapped_chest", - "blockRuntimeId" : 6097 + "blockRuntimeId" : 7133 }, { "id" : "minecraft:ender_chest", - "blockRuntimeId" : 4004 + "blockRuntimeId" : 4695 }, { "id" : "minecraft:barrel", - "blockRuntimeId" : 185 + "blockRuntimeId" : 201 }, { "id" : "minecraft:undyed_shulker_box", - "blockRuntimeId" : 6179 + "blockRuntimeId" : 7216 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5565 + "blockRuntimeId" : 6584 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5573 + "blockRuntimeId" : 6592 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5572 + "blockRuntimeId" : 6591 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5580 + "blockRuntimeId" : 6599 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5577 + "blockRuntimeId" : 6596 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5579 + "blockRuntimeId" : 6598 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5566 + "blockRuntimeId" : 6585 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5569 + "blockRuntimeId" : 6588 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5570 + "blockRuntimeId" : 6589 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5578 + "blockRuntimeId" : 6597 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5574 + "blockRuntimeId" : 6593 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5568 + "blockRuntimeId" : 6587 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5576 + "blockRuntimeId" : 6595 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5575 + "blockRuntimeId" : 6594 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5567 + "blockRuntimeId" : 6586 }, { "id" : "minecraft:shulker_box", - "blockRuntimeId" : 5571 + "blockRuntimeId" : 6590 }, { "id" : "minecraft:armor_stand" }, { "id" : "minecraft:noteblock", - "blockRuntimeId" : 4765 + "blockRuntimeId" : 5579 }, { "id" : "minecraft:jukebox", - "blockRuntimeId" : 4327 + "blockRuntimeId" : 5113 }, { "id" : "minecraft:music_disc_13" @@ -3610,15 +3984,15 @@ }, { "id" : "minecraft:glowstone", - "blockRuntimeId" : 4117 + "blockRuntimeId" : 4899 }, { "id" : "minecraft:redstone_lamp", - "blockRuntimeId" : 5452 + "blockRuntimeId" : 6469 }, { "id" : "minecraft:sealantern", - "blockRuntimeId" : 5563 + "blockRuntimeId" : 6582 }, { "id" : "minecraft:oak_sign" @@ -3683,6 +4057,15 @@ { "id" : "minecraft:pufferfish_bucket" }, + { + "id" : "minecraft:powder_snow_bucket" + }, + { + "id" : "minecraft:axolotl_bucket" + }, + { + "id" : "minecraft:glow_frame" + }, { "id" : "minecraft:skull", "damage" : 3 @@ -3708,23 +4091,23 @@ }, { "id" : "minecraft:beacon", - "blockRuntimeId" : 201 + "blockRuntimeId" : 217 }, { "id" : "minecraft:bell", - "blockRuntimeId" : 276 + "blockRuntimeId" : 292 }, { "id" : "minecraft:conduit", - "blockRuntimeId" : 3324 + "blockRuntimeId" : 3635 }, { "id" : "minecraft:stonecutter_block", - "blockRuntimeId" : 6014 + "blockRuntimeId" : 7049 }, { "id" : "minecraft:end_portal_frame", - "blockRuntimeId" : 3989 + "blockRuntimeId" : 4680 }, { "id" : "minecraft:coal" @@ -3738,6 +4121,18 @@ { "id" : "minecraft:iron_nugget" }, + { + "id" : "minecraft:raw_iron" + }, + { + "id" : "minecraft:raw_gold" + }, + { + "id" : "minecraft:raw_copper" + }, + { + "id" : "minecraft:copper_ingot" + }, { "id" : "minecraft:iron_ingot" }, @@ -3771,6 +4166,9 @@ { "id" : "minecraft:prismarine_shard" }, + { + "id" : "minecraft:amethyst_shard" + }, { "id" : "minecraft:prismarine_crystals" }, @@ -3845,7 +4243,11 @@ }, { "id" : "minecraft:end_rod", - "blockRuntimeId" : 3997 + "blockRuntimeId" : 4688 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 5401 }, { "id" : "minecraft:end_crystal" @@ -4307,15 +4709,15 @@ }, { "id" : "minecraft:rail", - "blockRuntimeId" : 5386 + "blockRuntimeId" : 6400 }, { "id" : "minecraft:golden_rail", - "blockRuntimeId" : 4120 + "blockRuntimeId" : 4902 }, { "id" : "minecraft:detector_rail", - "blockRuntimeId" : 3724 + "blockRuntimeId" : 4411 }, { "id" : "minecraft:activator_rail", @@ -4338,74 +4740,74 @@ }, { "id" : "minecraft:redstone_block", - "blockRuntimeId" : 5451 + "blockRuntimeId" : 6468 }, { "id" : "minecraft:redstone_torch", - "blockRuntimeId" : 5454 + "blockRuntimeId" : 6471 }, { "id" : "minecraft:lever", - "blockRuntimeId" : 4548 + "blockRuntimeId" : 5347 }, { "id" : "minecraft:wooden_button", - "blockRuntimeId" : 6480 + "blockRuntimeId" : 7587 }, { "id" : "minecraft:spruce_button", - "blockRuntimeId" : 5685 + "blockRuntimeId" : 6720 }, { "id" : "minecraft:birch_button", - "blockRuntimeId" : 308 + "blockRuntimeId" : 356 }, { "id" : "minecraft:jungle_button", - "blockRuntimeId" : 4328 + "blockRuntimeId" : 5114 }, { "id" : "minecraft:acacia_button" }, { "id" : "minecraft:dark_oak_button", - "blockRuntimeId" : 3560 + "blockRuntimeId" : 3886 }, { "id" : "minecraft:stone_button", - "blockRuntimeId" : 5914 + "blockRuntimeId" : 6949 }, { "id" : "minecraft:crimson_button", - "blockRuntimeId" : 3416 + "blockRuntimeId" : 3731 }, { "id" : "minecraft:warped_button", - "blockRuntimeId" : 6247 + "blockRuntimeId" : 7284 }, { "id" : "minecraft:polished_blackstone_button", - "blockRuntimeId" : 5006 + "blockRuntimeId" : 5844 }, { "id" : "minecraft:tripwire_hook", - "blockRuntimeId" : 6119 + "blockRuntimeId" : 7155 }, { "id" : "minecraft:wooden_pressure_plate", - "blockRuntimeId" : 6524 + "blockRuntimeId" : 7631 }, { "id" : "minecraft:spruce_pressure_plate", - "blockRuntimeId" : 5745 + "blockRuntimeId" : 6780 }, { "id" : "minecraft:birch_pressure_plate", - "blockRuntimeId" : 368 + "blockRuntimeId" : 416 }, { "id" : "minecraft:jungle_pressure_plate", - "blockRuntimeId" : 4388 + "blockRuntimeId" : 5174 }, { "id" : "minecraft:acacia_pressure_plate", @@ -4413,39 +4815,39 @@ }, { "id" : "minecraft:dark_oak_pressure_plate", - "blockRuntimeId" : 3620 + "blockRuntimeId" : 3946 }, { "id" : "minecraft:crimson_pressure_plate", - "blockRuntimeId" : 3485 + "blockRuntimeId" : 3800 }, { "id" : "minecraft:warped_pressure_plate", - "blockRuntimeId" : 6316 + "blockRuntimeId" : 7353 }, { "id" : "minecraft:stone_pressure_plate", - "blockRuntimeId" : 5926 + "blockRuntimeId" : 6961 }, { "id" : "minecraft:light_weighted_pressure_plate", - "blockRuntimeId" : 4586 + "blockRuntimeId" : 5385 }, { "id" : "minecraft:heavy_weighted_pressure_plate", - "blockRuntimeId" : 4218 + "blockRuntimeId" : 5001 }, { "id" : "minecraft:polished_blackstone_pressure_plate", - "blockRuntimeId" : 5020 + "blockRuntimeId" : 5858 }, { "id" : "minecraft:observer", - "blockRuntimeId" : 4774 + "blockRuntimeId" : 5588 }, { "id" : "minecraft:daylight_detector", - "blockRuntimeId" : 3690 + "blockRuntimeId" : 4016 }, { "id" : "minecraft:repeater" @@ -4458,30 +4860,30 @@ }, { "id" : "minecraft:dropper", - "blockRuntimeId" : 3847 + "blockRuntimeId" : 4538 }, { "id" : "minecraft:dispenser", - "blockRuntimeId" : 3751 + "blockRuntimeId" : 4439 }, { "id" : "minecraft:piston", - "blockRuntimeId" : 4801 + "blockRuntimeId" : 5629 }, { "id" : "minecraft:sticky_piston", - "blockRuntimeId" : 5888 + "blockRuntimeId" : 6923 }, { "id" : "minecraft:tnt", - "blockRuntimeId" : 6071 + "blockRuntimeId" : 7107 }, { "id" : "minecraft:name_tag" }, { "id" : "minecraft:loom", - "blockRuntimeId" : 4651 + "blockRuntimeId" : 5457 }, { "id" : "minecraft:banner" @@ -4724,7 +5126,7 @@ }, { "id" : "minecraft:target", - "blockRuntimeId" : 6070 + "blockRuntimeId" : 7105 }, { "id" : "minecraft:lodestone_compass" diff --git a/connector/src/main/resources/bedrock/entity_identifiers.dat b/connector/src/main/resources/bedrock/entity_identifiers.dat index c9477ba85dd..b9da310f500 100644 Binary files a/connector/src/main/resources/bedrock/entity_identifiers.dat and b/connector/src/main/resources/bedrock/entity_identifiers.dat differ diff --git a/connector/src/main/resources/bedrock/runtime_item_states.json b/connector/src/main/resources/bedrock/runtime_item_states.json index eaf6656bd0e..d52495ed454 100644 --- a/connector/src/main/resources/bedrock/runtime_item_states.json +++ b/connector/src/main/resources/bedrock/runtime_item_states.json @@ -1,7 +1,7 @@ [ { "name" : "minecraft:acacia_boat", - "id" : 377 + "id" : 379 }, { "name" : "minecraft:acacia_button", @@ -9,7 +9,7 @@ }, { "name" : "minecraft:acacia_door", - "id" : 546 + "id" : 556 }, { "name" : "minecraft:acacia_fence_gate", @@ -21,7 +21,7 @@ }, { "name" : "minecraft:acacia_sign", - "id" : 569 + "id" : 579 }, { "name" : "minecraft:acacia_stairs", @@ -45,7 +45,7 @@ }, { "name" : "minecraft:agent_spawn_egg", - "id" : 485 + "id" : 487 }, { "name" : "minecraft:air", @@ -55,6 +55,18 @@ "name" : "minecraft:allow", "id" : 210 }, + { + "name" : "minecraft:amethyst_block", + "id" : -327 + }, + { + "name" : "minecraft:amethyst_cluster", + "id" : -329 + }, + { + "name" : "minecraft:amethyst_shard", + "id" : 623 + }, { "name" : "minecraft:ancient_debris", "id" : -271 @@ -73,19 +85,39 @@ }, { "name" : "minecraft:armor_stand", - "id" : 542 + "id" : 552 }, { "name" : "minecraft:arrow", "id" : 301 }, + { + "name" : "minecraft:axolotl_bucket", + "id" : 369 + }, + { + "name" : "minecraft:axolotl_spawn_egg", + "id" : 500 + }, + { + "name" : "minecraft:azalea", + "id" : -337 + }, + { + "name" : "minecraft:azalea_leaves", + "id" : -324 + }, + { + "name" : "minecraft:azalea_leaves_flowered", + "id" : -325 + }, { "name" : "minecraft:baked_potato", "id" : 281 }, { "name" : "minecraft:balloon", - "id" : 587 + "id" : 597 }, { "name" : "minecraft:bamboo", @@ -97,11 +129,11 @@ }, { "name" : "minecraft:banner", - "id" : 557 + "id" : 567 }, { "name" : "minecraft:banner_pattern", - "id" : 613 + "id" : 627 }, { "name" : "minecraft:barrel", @@ -117,7 +149,7 @@ }, { "name" : "minecraft:bat_spawn_egg", - "id" : 451 + "id" : 453 }, { "name" : "minecraft:beacon", @@ -125,7 +157,7 @@ }, { "name" : "minecraft:bed", - "id" : 416 + "id" : 418 }, { "name" : "minecraft:bedrock", @@ -137,7 +169,7 @@ }, { "name" : "minecraft:bee_spawn_egg", - "id" : 492 + "id" : 494 }, { "name" : "minecraft:beef", @@ -163,9 +195,13 @@ "name" : "minecraft:bell", "id" : -206 }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, { "name" : "minecraft:birch_boat", - "id" : 374 + "id" : 376 }, { "name" : "minecraft:birch_button", @@ -173,7 +209,7 @@ }, { "name" : "minecraft:birch_door", - "id" : 544 + "id" : 554 }, { "name" : "minecraft:birch_fence_gate", @@ -185,7 +221,7 @@ }, { "name" : "minecraft:birch_sign", - "id" : 567 + "id" : 577 }, { "name" : "minecraft:birch_stairs", @@ -205,7 +241,7 @@ }, { "name" : "minecraft:black_dye", - "id" : 393 + "id" : 395 }, { "name" : "minecraft:black_glazed_terracotta", @@ -237,23 +273,23 @@ }, { "name" : "minecraft:blaze_powder", - "id" : 427 + "id" : 429 }, { "name" : "minecraft:blaze_rod", - "id" : 421 + "id" : 423 }, { "name" : "minecraft:blaze_spawn_egg", - "id" : 454 + "id" : 456 }, { "name" : "minecraft:bleach", - "id" : 585 + "id" : 595 }, { "name" : "minecraft:blue_dye", - "id" : 397 + "id" : 399 }, { "name" : "minecraft:blue_glazed_terracotta", @@ -265,11 +301,11 @@ }, { "name" : "minecraft:boat", - "id" : 611 + "id" : 625 }, { "name" : "minecraft:bone", - "id" : 413 + "id" : 415 }, { "name" : "minecraft:bone_block", @@ -277,11 +313,11 @@ }, { "name" : "minecraft:bone_meal", - "id" : 409 + "id" : 411 }, { "name" : "minecraft:book", - "id" : 385 + "id" : 387 }, { "name" : "minecraft:bookshelf", @@ -293,7 +329,7 @@ }, { "name" : "minecraft:bordure_indented_banner_pattern", - "id" : 576 + "id" : 586 }, { "name" : "minecraft:bow", @@ -309,7 +345,7 @@ }, { "name" : "minecraft:brewing_stand", - "id" : 429 + "id" : 431 }, { "name" : "minecraft:brewingstandblock", @@ -317,7 +353,7 @@ }, { "name" : "minecraft:brick", - "id" : 381 + "id" : 383 }, { "name" : "minecraft:brick_block", @@ -329,7 +365,7 @@ }, { "name" : "minecraft:brown_dye", - "id" : 396 + "id" : 398 }, { "name" : "minecraft:brown_glazed_terracotta", @@ -351,21 +387,29 @@ "name" : "minecraft:bucket", "id" : 360 }, + { + "name" : "minecraft:budding_amethyst", + "id" : -328 + }, { "name" : "minecraft:cactus", "id" : 81 }, { "name" : "minecraft:cake", - "id" : 415 + "id" : 417 + }, + { + "name" : "minecraft:calcite", + "id" : -326 }, { "name" : "minecraft:camera", - "id" : 582 + "id" : 592 }, { "name" : "minecraft:campfire", - "id" : 578 + "id" : 588 }, { "name" : "minecraft:carpet", @@ -377,7 +421,7 @@ }, { "name" : "minecraft:carrot_on_a_stick", - "id" : 507 + "id" : 517 }, { "name" : "minecraft:carrots", @@ -393,19 +437,31 @@ }, { "name" : "minecraft:cat_spawn_egg", - "id" : 486 + "id" : 488 }, { "name" : "minecraft:cauldron", - "id" : 430 + "id" : 432 }, { "name" : "minecraft:cave_spider_spawn_egg", - "id" : 455 + "id" : 457 + }, + { + "name" : "minecraft:cave_vines", + "id" : -322 + }, + { + "name" : "minecraft:cave_vines_body_with_berries", + "id" : -375 + }, + { + "name" : "minecraft:cave_vines_head_with_berries", + "id" : -376 }, { "name" : "minecraft:chain", - "id" : 607 + "id" : 617 }, { "name" : "minecraft:chain_command_block", @@ -445,7 +501,7 @@ }, { "name" : "minecraft:chest_minecart", - "id" : 387 + "id" : 389 }, { "name" : "minecraft:chicken", @@ -453,7 +509,11 @@ }, { "name" : "minecraft:chicken_spawn_egg", - "id" : 433 + "id" : 435 + }, + { + "name" : "minecraft:chiseled_deepslate", + "id" : -395 }, { "name" : "minecraft:chiseled_nether_bricks", @@ -469,7 +529,7 @@ }, { "name" : "minecraft:chorus_fruit", - "id" : 548 + "id" : 558 }, { "name" : "minecraft:chorus_plant", @@ -481,11 +541,11 @@ }, { "name" : "minecraft:clay_ball", - "id" : 382 + "id" : 384 }, { "name" : "minecraft:clock", - "id" : 391 + "id" : 393 }, { "name" : "minecraft:coal", @@ -499,6 +559,26 @@ "name" : "minecraft:coal_ore", "id" : 16 }, + { + "name" : "minecraft:cobbled_deepslate", + "id" : -379 + }, + { + "name" : "minecraft:cobbled_deepslate_double_slab", + "id" : -396 + }, + { + "name" : "minecraft:cobbled_deepslate_slab", + "id" : -380 + }, + { + "name" : "minecraft:cobbled_deepslate_stairs", + "id" : -381 + }, + { + "name" : "minecraft:cobbled_deepslate_wall", + "id" : -382 + }, { "name" : "minecraft:cobblestone", "id" : 4 @@ -513,7 +593,7 @@ }, { "name" : "minecraft:cocoa_beans", - "id" : 410 + "id" : 412 }, { "name" : "minecraft:cod", @@ -525,7 +605,7 @@ }, { "name" : "minecraft:cod_spawn_egg", - "id" : 478 + "id" : 480 }, { "name" : "minecraft:colored_torch_bp", @@ -541,15 +621,15 @@ }, { "name" : "minecraft:command_block_minecart", - "id" : 553 + "id" : 563 }, { "name" : "minecraft:comparator", - "id" : 512 + "id" : 522 }, { "name" : "minecraft:compass", - "id" : 389 + "id" : 391 }, { "name" : "minecraft:composter", @@ -557,7 +637,7 @@ }, { "name" : "minecraft:compound", - "id" : 583 + "id" : 593 }, { "name" : "minecraft:concrete", @@ -585,7 +665,7 @@ }, { "name" : "minecraft:cooked_mutton", - "id" : 541 + "id" : 551 }, { "name" : "minecraft:cooked_porkchop", @@ -603,6 +683,18 @@ "name" : "minecraft:cookie", "id" : 271 }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 504 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, { "name" : "minecraft:coral", "id" : -131 @@ -633,7 +725,15 @@ }, { "name" : "minecraft:cow_spawn_egg", - "id" : 434 + "id" : 436 + }, + { + "name" : "minecraft:cracked_deepslate_bricks", + "id" : -410 + }, + { + "name" : "minecraft:cracked_deepslate_tiles", + "id" : -409 }, { "name" : "minecraft:cracked_nether_bricks", @@ -649,11 +749,11 @@ }, { "name" : "minecraft:creeper_banner_pattern", - "id" : 572 + "id" : 582 }, { "name" : "minecraft:creeper_spawn_egg", - "id" : 439 + "id" : 441 }, { "name" : "minecraft:crimson_button", @@ -661,7 +761,7 @@ }, { "name" : "minecraft:crimson_door", - "id" : 604 + "id" : 614 }, { "name" : "minecraft:crimson_double_slab", @@ -701,7 +801,7 @@ }, { "name" : "minecraft:crimson_sign", - "id" : 602 + "id" : 612 }, { "name" : "minecraft:crimson_slab", @@ -729,15 +829,27 @@ }, { "name" : "minecraft:crossbow", - "id" : 565 + "id" : 575 }, { "name" : "minecraft:crying_obsidian", "id" : -289 }, + { + "name" : "minecraft:cut_copper", + "id" : -347 + }, + { + "name" : "minecraft:cut_copper_slab", + "id" : -361 + }, + { + "name" : "minecraft:cut_copper_stairs", + "id" : -354 + }, { "name" : "minecraft:cyan_dye", - "id" : 399 + "id" : 401 }, { "name" : "minecraft:cyan_glazed_terracotta", @@ -745,7 +857,7 @@ }, { "name" : "minecraft:dark_oak_boat", - "id" : 378 + "id" : 380 }, { "name" : "minecraft:dark_oak_button", @@ -753,7 +865,7 @@ }, { "name" : "minecraft:dark_oak_door", - "id" : 547 + "id" : 557 }, { "name" : "minecraft:dark_oak_fence_gate", @@ -765,7 +877,7 @@ }, { "name" : "minecraft:dark_oak_sign", - "id" : 570 + "id" : 580 }, { "name" : "minecraft:dark_oak_stairs", @@ -799,6 +911,82 @@ "name" : "minecraft:deadbush", "id" : 32 }, + { + "name" : "minecraft:deepslate", + "id" : -378 + }, + { + "name" : "minecraft:deepslate_brick_double_slab", + "id" : -399 + }, + { + "name" : "minecraft:deepslate_brick_slab", + "id" : -392 + }, + { + "name" : "minecraft:deepslate_brick_stairs", + "id" : -393 + }, + { + "name" : "minecraft:deepslate_brick_wall", + "id" : -394 + }, + { + "name" : "minecraft:deepslate_bricks", + "id" : -391 + }, + { + "name" : "minecraft:deepslate_coal_ore", + "id" : -406 + }, + { + "name" : "minecraft:deepslate_copper_ore", + "id" : -408 + }, + { + "name" : "minecraft:deepslate_diamond_ore", + "id" : -405 + }, + { + "name" : "minecraft:deepslate_emerald_ore", + "id" : -407 + }, + { + "name" : "minecraft:deepslate_gold_ore", + "id" : -402 + }, + { + "name" : "minecraft:deepslate_iron_ore", + "id" : -401 + }, + { + "name" : "minecraft:deepslate_lapis_ore", + "id" : -400 + }, + { + "name" : "minecraft:deepslate_redstone_ore", + "id" : -403 + }, + { + "name" : "minecraft:deepslate_tile_double_slab", + "id" : -398 + }, + { + "name" : "minecraft:deepslate_tile_slab", + "id" : -388 + }, + { + "name" : "minecraft:deepslate_tile_stairs", + "id" : -389 + }, + { + "name" : "minecraft:deepslate_tile_wall", + "id" : -390 + }, + { + "name" : "minecraft:deepslate_tiles", + "id" : -387 + }, { "name" : "minecraft:deny", "id" : 211 @@ -837,7 +1025,7 @@ }, { "name" : "minecraft:diamond_horse_armor", - "id" : 523 + "id" : 533 }, { "name" : "minecraft:diamond_leggings", @@ -867,17 +1055,25 @@ "name" : "minecraft:dirt", "id" : 3 }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, { "name" : "minecraft:dispenser", "id" : 23 }, { "name" : "minecraft:dolphin_spawn_egg", - "id" : 482 + "id" : 484 }, { "name" : "minecraft:donkey_spawn_egg", - "id" : 463 + "id" : 465 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 }, { "name" : "minecraft:double_plant", @@ -905,7 +1101,7 @@ }, { "name" : "minecraft:dragon_breath", - "id" : 550 + "id" : 560 }, { "name" : "minecraft:dragon_egg", @@ -919,25 +1115,29 @@ "name" : "minecraft:dried_kelp_block", "id" : -139 }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, { "name" : "minecraft:dropper", "id" : 125 }, { "name" : "minecraft:drowned_spawn_egg", - "id" : 481 + "id" : 483 }, { "name" : "minecraft:dye", - "id" : 612 + "id" : 626 }, { "name" : "minecraft:egg", - "id" : 388 + "id" : 390 }, { "name" : "minecraft:elder_guardian_spawn_egg", - "id" : 469 + "id" : 471 }, { "name" : "minecraft:element_0", @@ -1417,11 +1617,11 @@ }, { "name" : "minecraft:elytra", - "id" : 554 + "id" : 564 }, { "name" : "minecraft:emerald", - "id" : 502 + "id" : 512 }, { "name" : "minecraft:emerald_block", @@ -1433,11 +1633,11 @@ }, { "name" : "minecraft:empty_map", - "id" : 505 + "id" : 515 }, { "name" : "minecraft:enchanted_book", - "id" : 511 + "id" : 521 }, { "name" : "minecraft:enchanted_golden_apple", @@ -1457,7 +1657,7 @@ }, { "name" : "minecraft:end_crystal", - "id" : 615 + "id" : 629 }, { "name" : "minecraft:end_gateway", @@ -1485,35 +1685,55 @@ }, { "name" : "minecraft:ender_eye", - "id" : 431 + "id" : 433 }, { "name" : "minecraft:ender_pearl", - "id" : 420 + "id" : 422 }, { "name" : "minecraft:enderman_spawn_egg", - "id" : 440 + "id" : 442 }, { "name" : "minecraft:endermite_spawn_egg", - "id" : 458 + "id" : 460 }, { "name" : "minecraft:evoker_spawn_egg", - "id" : 473 + "id" : 475 }, { "name" : "minecraft:experience_bottle", - "id" : 498 + "id" : 508 }, { - "name" : "minecraft:farmland", - "id" : 60 + "name" : "minecraft:exposed_copper", + "id" : -341 }, { - "name" : "minecraft:feather", - "id" : 327 + "name" : "minecraft:exposed_cut_copper", + "id" : -348 + }, + { + "name" : "minecraft:exposed_cut_copper_slab", + "id" : -362 + }, + { + "name" : "minecraft:exposed_cut_copper_stairs", + "id" : -355 + }, + { + "name" : "minecraft:exposed_double_cut_copper_slab", + "id" : -369 + }, + { + "name" : "minecraft:farmland", + "id" : 60 + }, + { + "name" : "minecraft:feather", + "id" : 327 }, { "name" : "minecraft:fence", @@ -1525,15 +1745,15 @@ }, { "name" : "minecraft:fermented_spider_eye", - "id" : 426 + "id" : 428 }, { "name" : "minecraft:field_masoned_banner_pattern", - "id" : 575 + "id" : 585 }, { "name" : "minecraft:filled_map", - "id" : 418 + "id" : 420 }, { "name" : "minecraft:fire", @@ -1541,19 +1761,19 @@ }, { "name" : "minecraft:fire_charge", - "id" : 499 + "id" : 509 }, { "name" : "minecraft:firework_rocket", - "id" : 509 + "id" : 519 }, { "name" : "minecraft:firework_star", - "id" : 510 + "id" : 520 }, { "name" : "minecraft:fishing_rod", - "id" : 390 + "id" : 392 }, { "name" : "minecraft:fletching_table", @@ -1569,11 +1789,15 @@ }, { "name" : "minecraft:flower_banner_pattern", - "id" : 571 + "id" : 581 }, { "name" : "minecraft:flower_pot", - "id" : 504 + "id" : 514 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 }, { "name" : "minecraft:flowing_lava", @@ -1585,11 +1809,11 @@ }, { "name" : "minecraft:fox_spawn_egg", - "id" : 488 + "id" : 490 }, { "name" : "minecraft:frame", - "id" : 503 + "id" : 513 }, { "name" : "minecraft:frosted_ice", @@ -1601,11 +1825,11 @@ }, { "name" : "minecraft:ghast_spawn_egg", - "id" : 452 + "id" : 454 }, { "name" : "minecraft:ghast_tear", - "id" : 422 + "id" : 424 }, { "name" : "minecraft:gilded_blackstone", @@ -1617,7 +1841,7 @@ }, { "name" : "minecraft:glass_bottle", - "id" : 425 + "id" : 427 }, { "name" : "minecraft:glass_pane", @@ -1625,7 +1849,27 @@ }, { "name" : "minecraft:glistering_melon_slice", - "id" : 432 + "id" : 434 + }, + { + "name" : "minecraft:glow_berries", + "id" : 630 + }, + { + "name" : "minecraft:glow_frame", + "id" : 621 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 503 + }, + { + "name" : "minecraft:glow_lichen", + "id" : -411 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 502 }, { "name" : "minecraft:glow_stick", @@ -1641,7 +1885,15 @@ }, { "name" : "minecraft:glowstone_dust", - "id" : 392 + "id" : 394 + }, + { + "name" : "minecraft:goat_horn", + "id" : 622 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 501 }, { "name" : "minecraft:gold_block", @@ -1653,7 +1905,7 @@ }, { "name" : "minecraft:gold_nugget", - "id" : 423 + "id" : 425 }, { "name" : "minecraft:gold_ore", @@ -1689,7 +1941,7 @@ }, { "name" : "minecraft:golden_horse_armor", - "id" : 522 + "id" : 532 }, { "name" : "minecraft:golden_leggings", @@ -1729,7 +1981,7 @@ }, { "name" : "minecraft:gray_dye", - "id" : 401 + "id" : 403 }, { "name" : "minecraft:gray_glazed_terracotta", @@ -1737,7 +1989,7 @@ }, { "name" : "minecraft:green_dye", - "id" : 395 + "id" : 397 }, { "name" : "minecraft:green_glazed_terracotta", @@ -1749,12 +2001,16 @@ }, { "name" : "minecraft:guardian_spawn_egg", - "id" : 459 + "id" : 461 }, { "name" : "minecraft:gunpowder", "id" : 328 }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, { "name" : "minecraft:hard_glass", "id" : 253 @@ -1781,7 +2037,7 @@ }, { "name" : "minecraft:heart_of_the_sea", - "id" : 561 + "id" : 571 }, { "name" : "minecraft:heavy_weighted_pressure_plate", @@ -1789,7 +2045,7 @@ }, { "name" : "minecraft:hoglin_spawn_egg", - "id" : 494 + "id" : 496 }, { "name" : "minecraft:honey_block", @@ -1797,11 +2053,11 @@ }, { "name" : "minecraft:honey_bottle", - "id" : 581 + "id" : 591 }, { "name" : "minecraft:honeycomb", - "id" : 580 + "id" : 590 }, { "name" : "minecraft:honeycomb_block", @@ -1809,19 +2065,19 @@ }, { "name" : "minecraft:hopper", - "id" : 517 + "id" : 527 }, { "name" : "minecraft:hopper_minecart", - "id" : 516 + "id" : 526 }, { "name" : "minecraft:horse_spawn_egg", - "id" : 456 + "id" : 458 }, { "name" : "minecraft:husk_spawn_egg", - "id" : 461 + "id" : 463 }, { "name" : "minecraft:ice", @@ -1829,7 +2085,11 @@ }, { "name" : "minecraft:ice_bomb", - "id" : 584 + "id" : 594 + }, + { + "name" : "minecraft:infested_deepslate", + "id" : -454 }, { "name" : "minecraft:info_update", @@ -1841,7 +2101,7 @@ }, { "name" : "minecraft:ink_sac", - "id" : 411 + "id" : 413 }, { "name" : "minecraft:invisiblebedrock", @@ -1869,7 +2129,7 @@ }, { "name" : "minecraft:iron_door", - "id" : 370 + "id" : 372 }, { "name" : "minecraft:iron_helmet", @@ -1881,7 +2141,7 @@ }, { "name" : "minecraft:iron_horse_armor", - "id" : 521 + "id" : 531 }, { "name" : "minecraft:iron_ingot", @@ -1893,7 +2153,7 @@ }, { "name" : "minecraft:iron_nugget", - "id" : 559 + "id" : 569 }, { "name" : "minecraft:iron_ore", @@ -1967,6 +2227,10 @@ "name" : "minecraft:item.frame", "id" : 199 }, + { + "name" : "minecraft:item.glow_frame", + "id" : -339 + }, { "name" : "minecraft:item.hopper", "id" : 154 @@ -1983,10 +2247,6 @@ "name" : "minecraft:item.kelp", "id" : -138 }, - { - "name" : "minecraft:nether_brick", - "id" : 112 - }, { "name" : "minecraft:item.nether_sprouts", "id" : -238 @@ -2033,7 +2293,7 @@ }, { "name" : "minecraft:jungle_boat", - "id" : 375 + "id" : 377 }, { "name" : "minecraft:jungle_button", @@ -2041,7 +2301,7 @@ }, { "name" : "minecraft:jungle_door", - "id" : 545 + "id" : 555 }, { "name" : "minecraft:jungle_fence_gate", @@ -2053,7 +2313,7 @@ }, { "name" : "minecraft:jungle_sign", - "id" : 568 + "id" : 578 }, { "name" : "minecraft:jungle_stairs", @@ -2073,7 +2333,7 @@ }, { "name" : "minecraft:kelp", - "id" : 380 + "id" : 382 }, { "name" : "minecraft:ladder", @@ -2089,12 +2349,16 @@ }, { "name" : "minecraft:lapis_lazuli", - "id" : 412 + "id" : 414 }, { "name" : "minecraft:lapis_ore", "id" : 21 }, + { + "name" : "minecraft:large_amethyst_bud", + "id" : -330 + }, { "name" : "minecraft:lava", "id" : 11 @@ -2109,11 +2373,11 @@ }, { "name" : "minecraft:lead", - "id" : 537 + "id" : 547 }, { "name" : "minecraft:leather", - "id" : 379 + "id" : 381 }, { "name" : "minecraft:leather_boots", @@ -2129,7 +2393,7 @@ }, { "name" : "minecraft:leather_horse_armor", - "id" : 520 + "id" : 530 }, { "name" : "minecraft:leather_leggings", @@ -2157,7 +2421,7 @@ }, { "name" : "minecraft:light_blue_dye", - "id" : 405 + "id" : 407 }, { "name" : "minecraft:light_blue_glazed_terracotta", @@ -2165,15 +2429,19 @@ }, { "name" : "minecraft:light_gray_dye", - "id" : 400 + "id" : 402 }, { "name" : "minecraft:light_weighted_pressure_plate", "id" : 147 }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, { "name" : "minecraft:lime_dye", - "id" : 403 + "id" : 405 }, { "name" : "minecraft:lime_glazed_terracotta", @@ -2181,12 +2449,16 @@ }, { "name" : "minecraft:lingering_potion", - "id" : 552 + "id" : 562 }, { "name" : "minecraft:lit_blast_furnace", "id" : -214 }, + { + "name" : "minecraft:lit_deepslate_redstone_ore", + "id" : -404 + }, { "name" : "minecraft:lit_furnace", "id" : 62 @@ -2209,7 +2481,7 @@ }, { "name" : "minecraft:llama_spawn_egg", - "id" : 471 + "id" : 473 }, { "name" : "minecraft:lodestone", @@ -2217,7 +2489,7 @@ }, { "name" : "minecraft:lodestone_compass", - "id" : 590 + "id" : 600 }, { "name" : "minecraft:log", @@ -2233,7 +2505,7 @@ }, { "name" : "minecraft:magenta_dye", - "id" : 406 + "id" : 408 }, { "name" : "minecraft:magenta_glazed_terracotta", @@ -2245,15 +2517,19 @@ }, { "name" : "minecraft:magma_cream", - "id" : 428 + "id" : 430 }, { "name" : "minecraft:magma_cube_spawn_egg", - "id" : 453 + "id" : 455 }, { "name" : "minecraft:medicine", - "id" : 588 + "id" : 598 + }, + { + "name" : "minecraft:medium_amethyst_bud", + "id" : -331 }, { "name" : "minecraft:melon_block", @@ -2277,7 +2553,7 @@ }, { "name" : "minecraft:minecart", - "id" : 368 + "id" : 370 }, { "name" : "minecraft:mob_spawner", @@ -2285,7 +2561,7 @@ }, { "name" : "minecraft:mojang_banner_pattern", - "id" : 574 + "id" : 584 }, { "name" : "minecraft:monster_egg", @@ -2293,7 +2569,15 @@ }, { "name" : "minecraft:mooshroom_spawn_egg", - "id" : 438 + "id" : 440 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 }, { "name" : "minecraft:mossy_cobblestone", @@ -2313,7 +2597,7 @@ }, { "name" : "minecraft:mule_spawn_egg", - "id" : 464 + "id" : 466 }, { "name" : "minecraft:mushroom_stew", @@ -2321,59 +2605,59 @@ }, { "name" : "minecraft:music_disc_11", - "id" : 534 + "id" : 544 }, { "name" : "minecraft:music_disc_13", - "id" : 524 + "id" : 534 }, { "name" : "minecraft:music_disc_blocks", - "id" : 526 + "id" : 536 }, { "name" : "minecraft:music_disc_cat", - "id" : 525 + "id" : 535 }, { "name" : "minecraft:music_disc_chirp", - "id" : 527 + "id" : 537 }, { "name" : "minecraft:music_disc_far", - "id" : 528 + "id" : 538 }, { "name" : "minecraft:music_disc_mall", - "id" : 529 + "id" : 539 }, { "name" : "minecraft:music_disc_mellohi", - "id" : 530 + "id" : 540 }, { "name" : "minecraft:music_disc_pigstep", - "id" : 608 + "id" : 618 }, { "name" : "minecraft:music_disc_stal", - "id" : 531 + "id" : 541 }, { "name" : "minecraft:music_disc_strad", - "id" : 532 + "id" : 542 }, { "name" : "minecraft:music_disc_wait", - "id" : 535 + "id" : 545 }, { "name" : "minecraft:music_disc_ward", - "id" : 533 + "id" : 543 }, { "name" : "minecraft:mutton", - "id" : 540 + "id" : 550 }, { "name" : "minecraft:mycelium", @@ -2381,15 +2665,15 @@ }, { "name" : "minecraft:name_tag", - "id" : 538 + "id" : 548 }, { "name" : "minecraft:nautilus_shell", - "id" : 560 + "id" : 570 }, { - "name" : "minecraft:netherbrick", - "id" : 513 + "name" : "minecraft:nether_brick", + "id" : 112 }, { "name" : "minecraft:nether_brick_fence", @@ -2405,11 +2689,11 @@ }, { "name" : "minecraft:nether_sprouts", - "id" : 609 + "id" : 619 }, { "name" : "minecraft:nether_star", - "id" : 508 + "id" : 518 }, { "name" : "minecraft:nether_wart", @@ -2419,9 +2703,13 @@ "name" : "minecraft:nether_wart_block", "id" : 214 }, + { + "name" : "minecraft:netherbrick", + "id" : 523 + }, { "name" : "minecraft:netherite_axe", - "id" : 595 + "id" : 605 }, { "name" : "minecraft:netherite_block", @@ -2429,43 +2717,43 @@ }, { "name" : "minecraft:netherite_boots", - "id" : 600 + "id" : 610 }, { "name" : "minecraft:netherite_chestplate", - "id" : 598 + "id" : 608 }, { "name" : "minecraft:netherite_helmet", - "id" : 597 + "id" : 607 }, { "name" : "minecraft:netherite_hoe", - "id" : 596 + "id" : 606 }, { "name" : "minecraft:netherite_ingot", - "id" : 591 + "id" : 601 }, { "name" : "minecraft:netherite_leggings", - "id" : 599 + "id" : 609 }, { "name" : "minecraft:netherite_pickaxe", - "id" : 594 + "id" : 604 }, { "name" : "minecraft:netherite_scrap", - "id" : 601 + "id" : 611 }, { "name" : "minecraft:netherite_shovel", - "id" : 593 + "id" : 603 }, { "name" : "minecraft:netherite_sword", - "id" : 592 + "id" : 602 }, { "name" : "minecraft:netherrack", @@ -2485,11 +2773,11 @@ }, { "name" : "minecraft:npc_spawn_egg", - "id" : 468 + "id" : 470 }, { "name" : "minecraft:oak_boat", - "id" : 373 + "id" : 375 }, { "name" : "minecraft:oak_sign", @@ -2509,16 +2797,36 @@ }, { "name" : "minecraft:ocelot_spawn_egg", - "id" : 449 + "id" : 451 }, { "name" : "minecraft:orange_dye", - "id" : 407 + "id" : 409 }, { "name" : "minecraft:orange_glazed_terracotta", "id" : 221 }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "name" : "minecraft:oxidized_cut_copper", + "id" : -350 + }, + { + "name" : "minecraft:oxidized_cut_copper_slab", + "id" : -364 + }, + { + "name" : "minecraft:oxidized_cut_copper_stairs", + "id" : -357 + }, + { + "name" : "minecraft:oxidized_double_cut_copper_slab", + "id" : -371 + }, { "name" : "minecraft:packed_ice", "id" : 174 @@ -2529,47 +2837,47 @@ }, { "name" : "minecraft:panda_spawn_egg", - "id" : 487 + "id" : 489 }, { "name" : "minecraft:paper", - "id" : 384 + "id" : 386 }, { "name" : "minecraft:parrot_spawn_egg", - "id" : 476 + "id" : 478 }, { "name" : "minecraft:phantom_membrane", - "id" : 564 + "id" : 574 }, { "name" : "minecraft:phantom_spawn_egg", - "id" : 484 + "id" : 486 }, { "name" : "minecraft:pig_spawn_egg", - "id" : 435 + "id" : 437 }, { "name" : "minecraft:piglin_banner_pattern", - "id" : 577 + "id" : 587 }, { "name" : "minecraft:piglin_brute_spawn_egg", - "id" : 497 + "id" : 499 }, { "name" : "minecraft:piglin_spawn_egg", - "id" : 495 + "id" : 497 }, { "name" : "minecraft:pillager_spawn_egg", - "id" : 489 + "id" : 491 }, { "name" : "minecraft:pink_dye", - "id" : 402 + "id" : 404 }, { "name" : "minecraft:pink_glazed_terracotta", @@ -2591,13 +2899,17 @@ "name" : "minecraft:podzol", "id" : 243 }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, { "name" : "minecraft:poisonous_potato", "id" : 282 }, { "name" : "minecraft:polar_bear_spawn_egg", - "id" : 470 + "id" : 472 }, { "name" : "minecraft:polished_andesite_stairs", @@ -2655,6 +2967,26 @@ "name" : "minecraft:polished_blackstone_wall", "id" : -297 }, + { + "name" : "minecraft:polished_deepslate", + "id" : -383 + }, + { + "name" : "minecraft:polished_deepslate_double_slab", + "id" : -397 + }, + { + "name" : "minecraft:polished_deepslate_slab", + "id" : -384 + }, + { + "name" : "minecraft:polished_deepslate_stairs", + "id" : -385 + }, + { + "name" : "minecraft:polished_deepslate_wall", + "id" : -386 + }, { "name" : "minecraft:polished_diorite_stairs", "id" : -173 @@ -2665,7 +2997,7 @@ }, { "name" : "minecraft:popped_chorus_fruit", - "id" : 549 + "id" : 559 }, { "name" : "minecraft:porkchop", @@ -2685,7 +3017,15 @@ }, { "name" : "minecraft:potion", - "id" : 424 + "id" : 426 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 }, { "name" : "minecraft:powered_comparator", @@ -2705,11 +3045,11 @@ }, { "name" : "minecraft:prismarine_crystals", - "id" : 539 + "id" : 549 }, { "name" : "minecraft:prismarine_shard", - "id" : 555 + "id" : 565 }, { "name" : "minecraft:prismarine_stairs", @@ -2725,7 +3065,7 @@ }, { "name" : "minecraft:pufferfish_spawn_egg", - "id" : 479 + "id" : 481 }, { "name" : "minecraft:pumpkin", @@ -2745,7 +3085,7 @@ }, { "name" : "minecraft:purple_dye", - "id" : 398 + "id" : 400 }, { "name" : "minecraft:purple_glazed_terracotta", @@ -2761,7 +3101,7 @@ }, { "name" : "minecraft:quartz", - "id" : 514 + "id" : 524 }, { "name" : "minecraft:quartz_block", @@ -2785,15 +3125,15 @@ }, { "name" : "minecraft:rabbit_foot", - "id" : 518 + "id" : 528 }, { "name" : "minecraft:rabbit_hide", - "id" : 519 + "id" : 529 }, { "name" : "minecraft:rabbit_spawn_egg", - "id" : 457 + "id" : 459 }, { "name" : "minecraft:rabbit_stew", @@ -2805,11 +3145,35 @@ }, { "name" : "minecraft:rapid_fertilizer", - "id" : 586 + "id" : 596 }, { "name" : "minecraft:ravager_spawn_egg", - "id" : 491 + "id" : 493 + }, + { + "name" : "minecraft:raw_copper", + "id" : 507 + }, + { + "name" : "minecraft:raw_copper_block", + "id" : -452 + }, + { + "name" : "minecraft:raw_gold", + "id" : 506 + }, + { + "name" : "minecraft:raw_gold_block", + "id" : -453 + }, + { + "name" : "minecraft:raw_iron", + "id" : 505 + }, + { + "name" : "minecraft:raw_iron_block", + "id" : -451 }, { "name" : "minecraft:real_double_stone_slab", @@ -2829,7 +3193,7 @@ }, { "name" : "minecraft:red_dye", - "id" : 394 + "id" : 396 }, { "name" : "minecraft:red_flower", @@ -2865,7 +3229,7 @@ }, { "name" : "minecraft:redstone", - "id" : 371 + "id" : 373 }, { "name" : "minecraft:redstone_block", @@ -2889,7 +3253,7 @@ }, { "name" : "minecraft:repeater", - "id" : 417 + "id" : 419 }, { "name" : "minecraft:repeating_command_block", @@ -2909,7 +3273,7 @@ }, { "name" : "minecraft:saddle", - "id" : 369 + "id" : 371 }, { "name" : "minecraft:salmon", @@ -2921,7 +3285,7 @@ }, { "name" : "minecraft:salmon_spawn_egg", - "id" : 480 + "id" : 482 }, { "name" : "minecraft:sand", @@ -2943,9 +3307,13 @@ "name" : "minecraft:scaffolding", "id" : -165 }, + { + "name" : "minecraft:sculk_sensor", + "id" : -307 + }, { "name" : "minecraft:scute", - "id" : 562 + "id" : 572 }, { "name" : "minecraft:sea_pickle", @@ -2961,11 +3329,11 @@ }, { "name" : "minecraft:shears", - "id" : 419 + "id" : 421 }, { "name" : "minecraft:sheep_spawn_egg", - "id" : 436 + "id" : 438 }, { "name" : "minecraft:shield", @@ -2981,11 +3349,11 @@ }, { "name" : "minecraft:shulker_shell", - "id" : 556 + "id" : 566 }, { "name" : "minecraft:shulker_spawn_egg", - "id" : 467 + "id" : 469 }, { "name" : "minecraft:silver_glazed_terracotta", @@ -2993,23 +3361,23 @@ }, { "name" : "minecraft:silverfish_spawn_egg", - "id" : 441 + "id" : 443 }, { "name" : "minecraft:skeleton_horse_spawn_egg", - "id" : 465 + "id" : 467 }, { "name" : "minecraft:skeleton_spawn_egg", - "id" : 442 + "id" : 444 }, { "name" : "minecraft:skull", - "id" : 506 + "id" : 516 }, { "name" : "minecraft:skull_banner_pattern", - "id" : 573 + "id" : 583 }, { "name" : "minecraft:slime", @@ -3017,11 +3385,19 @@ }, { "name" : "minecraft:slime_ball", - "id" : 386 + "id" : 388 }, { "name" : "minecraft:slime_spawn_egg", - "id" : 443 + "id" : 445 + }, + { + "name" : "minecraft:small_amethyst_bud", + "id" : -332 + }, + { + "name" : "minecraft:small_dripleaf_block", + "id" : -336 }, { "name" : "minecraft:smithing_table", @@ -3031,6 +3407,10 @@ "name" : "minecraft:smoker", "id" : -198 }, + { + "name" : "minecraft:smooth_basalt", + "id" : -377 + }, { "name" : "minecraft:smooth_quartz_stairs", "id" : -185 @@ -3057,11 +3437,11 @@ }, { "name" : "minecraft:snowball", - "id" : 372 + "id" : 374 }, { "name" : "minecraft:soul_campfire", - "id" : 610 + "id" : 620 }, { "name" : "minecraft:soul_fire", @@ -3085,11 +3465,11 @@ }, { "name" : "minecraft:sparkler", - "id" : 589 + "id" : 599 }, { "name" : "minecraft:spawn_egg", - "id" : 614 + "id" : 628 }, { "name" : "minecraft:spider_eye", @@ -3097,19 +3477,23 @@ }, { "name" : "minecraft:spider_spawn_egg", - "id" : 444 + "id" : 446 }, { "name" : "minecraft:splash_potion", - "id" : 551 + "id" : 561 }, { "name" : "minecraft:sponge", "id" : 19 }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, { "name" : "minecraft:spruce_boat", - "id" : 376 + "id" : 378 }, { "name" : "minecraft:spruce_button", @@ -3117,7 +3501,7 @@ }, { "name" : "minecraft:spruce_door", - "id" : 543 + "id" : 553 }, { "name" : "minecraft:spruce_fence_gate", @@ -3129,7 +3513,7 @@ }, { "name" : "minecraft:spruce_sign", - "id" : 566 + "id" : 576 }, { "name" : "minecraft:spruce_stairs", @@ -3147,9 +3531,13 @@ "name" : "minecraft:spruce_wall_sign", "id" : -182 }, + { + "name" : "minecraft:spyglass", + "id" : 624 + }, { "name" : "minecraft:squid_spawn_egg", - "id" : 448 + "id" : 450 }, { "name" : "minecraft:stained_glass", @@ -3237,11 +3625,11 @@ }, { "name" : "minecraft:stray_spawn_egg", - "id" : 460 + "id" : 462 }, { "name" : "minecraft:strider_spawn_egg", - "id" : 493 + "id" : 495 }, { "name" : "minecraft:string", @@ -3297,15 +3685,15 @@ }, { "name" : "minecraft:sugar", - "id" : 414 + "id" : 416 }, { "name" : "minecraft:sugar_cane", - "id" : 383 + "id" : 385 }, { "name" : "minecraft:suspicious_stew", - "id" : 579 + "id" : 589 }, { "name" : "minecraft:sweet_berries", @@ -3323,13 +3711,17 @@ "name" : "minecraft:target", "id" : -239 }, + { + "name" : "minecraft:tinted_glass", + "id" : -334 + }, { "name" : "minecraft:tnt", "id" : 46 }, { "name" : "minecraft:tnt_minecart", - "id" : 515 + "id" : 525 }, { "name" : "minecraft:torch", @@ -3337,7 +3729,7 @@ }, { "name" : "minecraft:totem_of_undying", - "id" : 558 + "id" : 568 }, { "name" : "minecraft:trapdoor", @@ -3349,7 +3741,7 @@ }, { "name" : "minecraft:trident", - "id" : 536 + "id" : 546 }, { "name" : "minecraft:tripwire", @@ -3369,7 +3761,11 @@ }, { "name" : "minecraft:tropical_fish_spawn_egg", - "id" : 477 + "id" : 479 + }, + { + "name" : "minecraft:tuff", + "id" : -333 }, { "name" : "minecraft:turtle_egg", @@ -3377,11 +3773,11 @@ }, { "name" : "minecraft:turtle_helmet", - "id" : 563 + "id" : 573 }, { "name" : "minecraft:turtle_spawn_egg", - "id" : 483 + "id" : 485 }, { "name" : "minecraft:twisting_vines", @@ -3413,15 +3809,15 @@ }, { "name" : "minecraft:vex_spawn_egg", - "id" : 474 + "id" : 476 }, { "name" : "minecraft:villager_spawn_egg", - "id" : 447 + "id" : 449 }, { "name" : "minecraft:vindicator_spawn_egg", - "id" : 472 + "id" : 474 }, { "name" : "minecraft:vine", @@ -3437,7 +3833,7 @@ }, { "name" : "minecraft:wandering_trader_spawn_egg", - "id" : 490 + "id" : 492 }, { "name" : "minecraft:warped_button", @@ -3445,7 +3841,7 @@ }, { "name" : "minecraft:warped_door", - "id" : 605 + "id" : 615 }, { "name" : "minecraft:warped_double_slab", @@ -3465,7 +3861,7 @@ }, { "name" : "minecraft:warped_fungus_on_a_stick", - "id" : 606 + "id" : 616 }, { "name" : "minecraft:warped_hyphae", @@ -3489,7 +3885,7 @@ }, { "name" : "minecraft:warped_sign", - "id" : 603 + "id" : 613 }, { "name" : "minecraft:warped_slab", @@ -3531,6 +3927,106 @@ "name" : "minecraft:waterlily", "id" : 111 }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "name" : "minecraft:waxed_cut_copper", + "id" : -351 + }, + { + "name" : "minecraft:waxed_cut_copper_slab", + "id" : -365 + }, + { + "name" : "minecraft:waxed_cut_copper_stairs", + "id" : -358 + }, + { + "name" : "minecraft:waxed_double_cut_copper_slab", + "id" : -372 + }, + { + "name" : "minecraft:waxed_exposed_copper", + "id" : -345 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper", + "id" : -352 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_slab", + "id" : -366 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_stairs", + "id" : -359 + }, + { + "name" : "minecraft:waxed_exposed_double_cut_copper_slab", + "id" : -373 + }, + { + "name" : "minecraft:waxed_oxidized_copper", + "id" : -446 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper", + "id" : -447 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_slab", + "id" : -449 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_stairs", + "id" : -448 + }, + { + "name" : "minecraft:waxed_oxidized_double_cut_copper_slab", + "id" : -450 + }, + { + "name" : "minecraft:waxed_weathered_copper", + "id" : -346 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper", + "id" : -353 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_slab", + "id" : -367 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_stairs", + "id" : -360 + }, + { + "name" : "minecraft:waxed_weathered_double_cut_copper_slab", + "id" : -374 + }, + { + "name" : "minecraft:weathered_copper", + "id" : -342 + }, + { + "name" : "minecraft:weathered_cut_copper", + "id" : -349 + }, + { + "name" : "minecraft:weathered_cut_copper_slab", + "id" : -363 + }, + { + "name" : "minecraft:weathered_cut_copper_stairs", + "id" : -356 + }, + { + "name" : "minecraft:weathered_double_cut_copper_slab", + "id" : -370 + }, { "name" : "minecraft:web", "id" : 30 @@ -3549,7 +4045,7 @@ }, { "name" : "minecraft:white_dye", - "id" : 408 + "id" : 410 }, { "name" : "minecraft:white_glazed_terracotta", @@ -3557,7 +4053,7 @@ }, { "name" : "minecraft:witch_spawn_egg", - "id" : 450 + "id" : 452 }, { "name" : "minecraft:wither_rose", @@ -3565,11 +4061,11 @@ }, { "name" : "minecraft:wither_skeleton_spawn_egg", - "id" : 462 + "id" : 464 }, { "name" : "minecraft:wolf_spawn_egg", - "id" : 437 + "id" : 439 }, { "name" : "minecraft:wood", @@ -3617,15 +4113,15 @@ }, { "name" : "minecraft:writable_book", - "id" : 500 + "id" : 510 }, { "name" : "minecraft:written_book", - "id" : 501 + "id" : 511 }, { "name" : "minecraft:yellow_dye", - "id" : 404 + "id" : 406 }, { "name" : "minecraft:yellow_flower", @@ -3637,22 +4133,22 @@ }, { "name" : "minecraft:zoglin_spawn_egg", - "id" : 496 + "id" : 498 }, { "name" : "minecraft:zombie_horse_spawn_egg", - "id" : 466 + "id" : 468 }, { "name" : "minecraft:zombie_pigman_spawn_egg", - "id" : 446 + "id" : 448 }, { "name" : "minecraft:zombie_spawn_egg", - "id" : 445 + "id" : 447 }, { "name" : "minecraft:zombie_villager_spawn_egg", - "id" : 475 + "id" : 477 } ] \ No newline at end of file diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 7133ec88d3b..79f7bb6bf4d 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -58,9 +58,9 @@ remote: forward-hostname: false # Floodgate uses encryption to ensure use from authorised sources. -# This should point to the public key generated by Floodgate (Bungee or CraftBukkit) +# This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity) # You can ignore this when not using Floodgate. -floodgate-key-file: public-key.pem +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. @@ -124,21 +124,16 @@ show-cooldown: title # Controls if coordinates are shown to players. show-coordinates: true +# 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 +# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen. +# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped +emote-offhand-workaround: "disabled" + # The default locale if we dont have the one the client requested. Uncomment to not use the default system language. # default-locale: en_us -# Configures if chunk caching should be enabled or not. This keeps an individual -# record of each block the client loads in. This feature does allow for a few things -# such as more accurate movement that causes less problems with anticheat (meaning -# you're less likely to be banned) and allows block break animations to show up in -# creative mode (and other features). Although this increases RAM usage, it likely -# won't have much of an effect for the vast majority of people. However, if you're -# running out of RAM or are in a RAM-sensitive environment, you may want to disable -# this. When using the Spigot version of Geyser, support for features or -# implementations this allows is automatically enabled without the additional caching -# as Geyser has direct access to the server itself. -cache-chunks: true - # Specify how many days images will be cached to disk to save downloading them from the internet. # A value of 0 is disabled. (Default: 0) cache-images: 0 diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 3d3b60de724..fcc8f01e25e 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 3d3b60de724f3f552f351c5f400269fde7598b67 +Subproject commit fcc8f01e25e481c9c82f2ee9bff23111ec781dd7 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index c846b8200eb..1c39b875c58 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit c846b8200eb8ebb37207666f7eddb83f2b636c37 +Subproject commit 1c39b875c58efe4ca37f6ff7d3ebe05781347642 diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index 7052123fe97..efa1a8b3d4f 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -46,7 +46,7 @@ public void setUp() throws Exception { // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §r§6The §r§d§k||§r§b§lCubeCraft§r§d§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§6The §r§c§k||§r§b§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§e§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§2§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§b(kinda sus) §r§e✦"); // Color code format resetting diff --git a/pom.xml b/pom.xml index f7daf548432..998cdd3e3d7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 1.2.1-SNAPSHOT + 1.4.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. @@ -61,10 +61,6 @@ true - - viaversion-repo - https://repo.viaversion.com - sonatype https://oss.sonatype.org/content/repositories/snapshots/