From 37d6fd8abfbc315c66e555b82343ba9e2b1db31a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 17 Sep 2021 09:39:49 -0400 Subject: [PATCH 01/38] Add an option to disable compression for Geyser clients --- .../GeyserBungeeCompressionDisabler.java | 52 ++++++++ .../bungeecord/GeyserBungeeInjector.java | 5 + .../GeyserSpigotCompressionDisabler.java | 114 ++++++++++++++++++ .../platform/spigot/GeyserSpigotInjector.java | 6 +- .../GeyserVelocityCompressionDisabler.java | 85 +++++++++++++ .../velocity/GeyserVelocityInjector.java | 17 ++- .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + core/src/main/resources/config.yml | 5 + 9 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java create mode 100644 bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java new file mode 100644 index 000000000..cf5e3cc5b --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.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.platform.bungeecord; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import net.md_5.bungee.protocol.packet.LoginSuccess; +import net.md_5.bungee.protocol.packet.SetCompression; + +public class GeyserBungeeCompressionDisabler extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof SetCompression)) { + if (msg instanceof LoginSuccess) { + // We're past the point that compression can be enabled + if (ctx.pipeline().get("compress") != null) { + ctx.pipeline().remove("compress"); + } + if (ctx.pipeline().get("decompress") != null) { + ctx.pipeline().remove("decompress"); + } + ctx.pipeline().remove(this); + } + super.write(ctx, msg, promise); + } + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java index 2b1fa10c0..2354e9753 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java @@ -140,6 +140,11 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener { channelInitializer = PipelineUtils.SERVER_CHILD; } initChannel.invoke(channelInitializer, ch); + + if (bootstrap.getGeyserConfig().isDisableCompression()) { + ch.pipeline().addAfter(PipelineUtils.PACKET_ENCODER, "geyser-compression-disabler", + new GeyserBungeeCompressionDisabler()); + } } }) .childAttr(listener, listenerInfo) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java new file mode 100644 index 000000000..2ee948496 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java @@ -0,0 +1,114 @@ +/* + * 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.platform.spigot; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import org.bukkit.Bukkit; +import org.geysermc.connector.GeyserConnector; + +/** + * Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients + * that won't be receiving the data over the network. + * + * As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written. + * If we simply "cancel" or don't forward the packet, then the listener is never called. + */ +public class GeyserSpigotCompressionDisabler extends ChannelOutboundHandlerAdapter { + public static final boolean ENABLED; + + private static final Class COMPRESSION_PACKET_CLASS; + private static final Class LOGIN_SUCCESS_PACKET_CLASS; + private static final boolean PROTOCOL_SUPPORT_INSTALLED; + + static { + PROTOCOL_SUPPORT_INSTALLED = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null; + + Class compressionPacketClass = null; + Class loginSuccessPacketClass = null; + boolean enabled = false; + try { + compressionPacketClass = findCompressionPacket(); + loginSuccessPacketClass = findLoginSuccessPacket(); + enabled = true; + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error("Could not initialize compression disabler!", e); + } + COMPRESSION_PACKET_CLASS = compressionPacketClass; + LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass; + ENABLED = enabled; + } + + public GeyserSpigotCompressionDisabler() { + if (!ENABLED) { + throw new RuntimeException("Geyser compression disabler cannot be initialized in its current state!"); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + Class msgClass = msg.getClass(); + // Don't let any compression packet get through + if (!COMPRESSION_PACKET_CLASS.isAssignableFrom(msgClass)) { + if (LOGIN_SUCCESS_PACKET_CLASS.isAssignableFrom(msgClass)) { + if (PROTOCOL_SUPPORT_INSTALLED) { + // ProtocolSupport must send the compression packet, so let's remove what it did before it does damage + if (ctx.pipeline().get("compress") != null) { + ctx.pipeline().remove("compress"); + } + if (ctx.pipeline().get("decompress") != null) { + ctx.pipeline().remove("decompress"); + } + } + // We're past the point that a compression packet can be sent, so we can safely yeet ourselves away + ctx.channel().pipeline().remove(this); + } + super.write(ctx, msg, promise); + } else if (PROTOCOL_SUPPORT_INSTALLED) { + // We must indicate it "succeeded" or ProtocolSupport will time us out + promise.setSuccess(); + } + } + + private static Class findCompressionPacket() throws ClassNotFoundException { + try { + return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSetCompression"); + } catch (ClassNotFoundException e) { + String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server"); + return Class.forName(prefix + ".PacketLoginOutSetCompression"); + } + } + + private static Class findLoginSuccessPacket() throws ClassNotFoundException { + try { + return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSuccess"); + } catch (ClassNotFoundException e) { + String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server"); + return Class.forName(prefix + ".PacketLoginOutSuccess"); + } + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java index 0fd8d849b..998d0a245 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -115,10 +115,14 @@ public class GeyserSpigotInjector extends GeyserInjector { ChannelFuture channelFuture = (new ServerBootstrap() .channel(LocalServerChannelWrapper.class) - .childHandler(new ChannelInitializer() { + .childHandler(new ChannelInitializer<>() { @Override protected void initChannel(Channel ch) throws Exception { initChannel.invoke(childHandler, ch); + + if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) { + ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler()); + } } }) // Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java new file mode 100644 index 000000000..4d8b7799c --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.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.platform.velocity; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import org.geysermc.connector.GeyserConnector; + +import java.lang.reflect.Method; + +public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAdapter { + public static final boolean ENABLED; + private static final Class COMPRESSION_PACKET_CLASS; + private static final Class LOGIN_SUCCESS_PACKET_CLASS; + private static final Method SET_COMPRESSION_METHOD; + + static { + boolean enabled = false; + Class compressionPacketClass = null; + Class loginSuccessPacketClass = null; + Method setCompressionMethod = null; + + try { + compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression"); + loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess"); + setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection") + .getMethod("setCompressionThreshold", int.class); + enabled = true; + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error("Could not initialize compression disabler!", e); + } + + ENABLED = enabled; + COMPRESSION_PACKET_CLASS = compressionPacketClass; + LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass; + SET_COMPRESSION_METHOD = setCompressionMethod; + } + + public GeyserVelocityCompressionDisabler() { + if (!ENABLED) { + throw new RuntimeException("Geyser compression disabler cannot be initialized in its current state!"); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + Class msgClass = msg.getClass(); + // instanceOf doesn't work. Unsure why. + if (COMPRESSION_PACKET_CLASS != msgClass) { + if (LOGIN_SUCCESS_PACKET_CLASS == msgClass) { + // We're past the point that compression can be enabled + // Invoke the method as it calls a Netty event and handles removing cleaner than we could + Object minecraftConnection = ctx.pipeline().get("handler"); + SET_COMPRESSION_METHOD.invoke(minecraftConnection, -1); + + ctx.pipeline().remove(this); + } + super.write(ctx, msg, promise); + } + } +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java index e1a1030e3..4ffb286b8 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.function.Supplier; public class GeyserVelocityInjector extends GeyserInjector { @@ -67,9 +68,23 @@ public class GeyserVelocityInjector extends GeyserInjector { workerGroupField.setAccessible(true); EventLoopGroup workerGroup = (EventLoopGroup) workerGroupField.get(connectionManager); + // This method is what initializes the connection in Java Edition, after Netty is all set. + Method initChannel = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + initChannel.setAccessible(true); + ChannelFuture channelFuture = (new ServerBootstrap() .channel(LocalServerChannelWrapper.class) - .childHandler(channelInitializer) + .childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) throws Exception { + initChannel.invoke(channelInitializer, ch); + + if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserVelocityCompressionDisabler.ENABLED) { + ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler", + new GeyserVelocityCompressionDisabler()); + } + } + }) .group(bossGroup, workerGroup) // Cannot be DefaultEventLoopGroup .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, serverWriteMark) // Required or else rare network freezes can occur .localAddress(LocalAddress.ANY)) diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 1f188cf40..10b21601a 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -182,6 +182,8 @@ public interface GeyserConfiguration { boolean isUseDirectConnection(); + boolean isDisableCompression(); + int getConfigVersion(); static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) { diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 30a947e53..3a6b5fff6 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -275,6 +275,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("use-direct-connection") private boolean useDirectConnection = true; + @JsonProperty("disable-compression") + private boolean isDisableCompression = true; + @JsonProperty("config-version") private int configVersion = 0; diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index d1a956187..76d65a2f0 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -207,4 +207,9 @@ mtu: 1400 # If disabled on plugin versions, expect performance decrease and latency increase use-direct-connection: true +# Whether Geyser should attempt to disable compression for Bedrock players. This should be a benefit as there is no need to compress data +# when Java packets aren't being handled over the network. +# This requires use-direct-connection to be true. +disable-compression: true + config-version: 4 From 2af10ad8bd772a361e42da9aa912110ba40292ce Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 18 Jun 2022 16:45:50 -0400 Subject: [PATCH 02/38] Update for 1.19 --- .../bungeecord/GeyserBungeeCompressionDisabler.java | 2 +- .../platform/bungeecord/GeyserBungeeInjector.java | 2 +- .../spigot/GeyserSpigotCompressionDisabler.java | 8 ++++---- .../velocity/GeyserVelocityCompressionDisabler.java | 13 ++++++------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java index cf5e3cc5b..084e1d2dc 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java index 2354e9753..04edbadde 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java @@ -168,7 +168,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener { // If native compression is enabled, then this line is tripped up if a heap buffer is sent over in such a situation // as a new direct buffer is not created with that patch (HeapByteBufs throw an UnsupportedOperationException here): // https://github.com/SpigotMC/BungeeCord/blob/a283aaf724d4c9a815540cd32f3aafaa72df9e05/native/src/main/java/net/md_5/bungee/jni/zlib/NativeZlib.java#L43 - // This issue could be mitigated down the line by preventing Bungee from setting compression + // If disable compression is enabled, this can probably be disabled now, but BungeeCord (not Waterfall) complains LocalSession.createDirectByteBufAllocator(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java index 2ee948496..9b112f62f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotCompressionDisabler.java @@ -23,13 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import org.bukkit.Bukkit; -import org.geysermc.connector.GeyserConnector; +import org.geysermc.geyser.GeyserImpl; /** * Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients @@ -39,7 +39,7 @@ import org.geysermc.connector.GeyserConnector; * If we simply "cancel" or don't forward the packet, then the listener is never called. */ public class GeyserSpigotCompressionDisabler extends ChannelOutboundHandlerAdapter { - public static final boolean ENABLED; + static final boolean ENABLED; private static final Class COMPRESSION_PACKET_CLASS; private static final Class LOGIN_SUCCESS_PACKET_CLASS; @@ -56,7 +56,7 @@ public class GeyserSpigotCompressionDisabler extends ChannelOutboundHandlerAdapt loginSuccessPacketClass = findLoginSuccessPacket(); enabled = true; } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error("Could not initialize compression disabler!", e); + GeyserImpl.getInstance().getLogger().error("Could not initialize compression disabler!", e); } COMPRESSION_PACKET_CLASS = compressionPacketClass; LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java index 4d8b7799c..760b68c31 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java @@ -23,17 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; -import org.geysermc.connector.GeyserConnector; +import org.geysermc.geyser.GeyserImpl; import java.lang.reflect.Method; public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAdapter { - public static final boolean ENABLED; + static final boolean ENABLED; private static final Class COMPRESSION_PACKET_CLASS; private static final Class LOGIN_SUCCESS_PACKET_CLASS; private static final Method SET_COMPRESSION_METHOD; @@ -51,7 +51,7 @@ public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAda .getMethod("setCompressionThreshold", int.class); enabled = true; } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error("Could not initialize compression disabler!", e); + GeyserImpl.getInstance().getLogger().error("Could not initialize compression disabler!", e); } ENABLED = enabled; @@ -69,9 +69,8 @@ public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAda @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { Class msgClass = msg.getClass(); - // instanceOf doesn't work. Unsure why. - if (COMPRESSION_PACKET_CLASS != msgClass) { - if (LOGIN_SUCCESS_PACKET_CLASS == msgClass) { + if (!COMPRESSION_PACKET_CLASS.isAssignableFrom(msgClass)) { + if (LOGIN_SUCCESS_PACKET_CLASS.isAssignableFrom(msgClass)) { // We're past the point that compression can be enabled // Invoke the method as it calls a Netty event and handles removing cleaner than we could Object minecraftConnection = ctx.pipeline().get("handler"); From 298d59405a77eb9ecceb94bc9b698ac3fc0db944 Mon Sep 17 00:00:00 2001 From: Valaphee Date: Thu, 15 Dec 2022 11:56:52 +0100 Subject: [PATCH 03/38] Fix wrong render distance calculation --- .../main/java/org/geysermc/geyser/session/GeyserSession.java | 1 - .../java/entity/player/JavaPlayerPositionTranslator.java | 2 +- .../main/java/org/geysermc/geyser/util/DimensionUtils.java | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 8f9bc394a..7d911e22c 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1378,7 +1378,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } public void setServerRenderDistance(int renderDistance) { - renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle this.serverRenderDistance = renderDistance; ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 2d2d7279f..0652792ef 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -85,7 +85,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator 47 && !session.isEmulatePost1_13Logic()) { + if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) { // See DimensionUtils for an explanation ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); chunkRadiusUpdatedPacket.setRadius(session.getServerRenderDistance()); diff --git a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java index 5963da703..6b7296c12 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -75,18 +75,17 @@ public class DimensionUtils { session.getPistonCache().clear(); session.getSkullCache().clear(); - if (session.getServerRenderDistance() > 47 && !session.isEmulatePost1_13Logic()) { + if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) { // The server-sided view distance wasn't a thing until Minecraft Java 1.14 // So ViaVersion compensates by sending a "view distance" of 64 // That's fine, except when the actual view distance sent from the server is five chunks // The client locks up when switching dimensions, expecting more chunks than it's getting // To solve this, we cap at 32 unless we know that the render distance actually exceeds 32 - // 47 is the Bedrock equivalent of 32 // Also, as of 1.19: PS4 crashes with a ChunkRadiusUpdatedPacket too large session.getGeyser().getLogger().debug("Applying dimension switching workaround for Bedrock render distance of " + session.getServerRenderDistance()); ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); - chunkRadiusUpdatedPacket.setRadius(47); + chunkRadiusUpdatedPacket.setRadius(32); session.sendUpstreamPacket(chunkRadiusUpdatedPacket); // Will be re-adjusted on spawn } From 47fd148b7e56fe1724f526eea18e31c0cd6874e8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 17 Dec 2022 14:52:20 -0500 Subject: [PATCH 04/38] Refactor player add code for 1.19.3 Players are now not always added into the tab list. --- .../entity/type/player/PlayerEntity.java | 8 +- .../type/player/SessionPlayerEntity.java | 5 + .../entity/type/player/SkullPlayerEntity.java | 1 - .../geyser/session/cache/EntityCache.java | 3 +- .../geyser/skin/FakeHeadProvider.java | 49 +------- .../org/geysermc/geyser/skin/SkinManager.java | 85 +++++++------ .../JavaPlayerInfoRemoveTranslator.java | 10 +- .../JavaPlayerInfoUpdateTranslator.java | 119 ++++++++++-------- 8 files changed, 136 insertions(+), 144 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 8e600b1a8..3e3a298bd 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -77,7 +77,6 @@ public class PlayerEntity extends LivingEntity { } private String username; - private boolean playerList = true; // Player is in the player list /** * The textures property from the GameProfile. @@ -417,4 +416,11 @@ public class PlayerEntity extends LivingEntity { session.sendUpstreamPacket(packet); } } + + /** + * @return the UUID that should be used when dealing with Bedrock's tab list. + */ + public UUID getTabListUuid() { + return getUuid(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 74b95b73c..99517b208 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -250,4 +250,9 @@ public class SessionPlayerEntity extends PlayerEntity { dirtyMetadata.put(EntityData.PLAYER_HAS_DIED, (byte) 0); } } + + @Override + public UUID getTabListUuid() { + return session.getAuthData().uuid(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index 176d171de..c2af2e36b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -48,7 +48,6 @@ public class SkullPlayerEntity extends PlayerEntity { public SkullPlayerEntity(GeyserSession session, long geyserId) { super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null); - setPlayerList(false); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 012606615..9dc89215a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -123,7 +123,8 @@ public class EntityCache { } public void addPlayerEntity(PlayerEntity entity) { - playerEntities.put(entity.getUuid(), entity); + // putIfAbsent matches the behavior of playerInfoMap in Java as of 1.19.3 + playerEntities.putIfAbsent(entity.getUuid(), entity); } public PlayerEntity getPlayerEntity(UUID uuid) { diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 6794af498..464f53d48 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -29,10 +29,6 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.nukkitx.protocol.bedrock.data.skin.ImageData; -import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -45,7 +41,6 @@ import org.geysermc.geyser.text.GeyserLocale; import javax.annotation.Nonnull; import java.awt.*; import java.awt.image.BufferedImage; -import java.util.Collections; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -111,7 +106,7 @@ public class FakeHeadProvider { try { SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); - sendSkinPacket(session, entity, mergedSkinData); + SkinManager.sendSkinPacket(session, entity, mergedSkinData); } catch (ExecutionException e) { GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e); } @@ -133,50 +128,10 @@ public class FakeHeadProvider { return; } - sendSkinPacket(session, entity, skinData); + SkinManager.sendSkinPacket(session, entity, skinData); }); } - private static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinProvider.SkinData skinData) { - SkinProvider.Skin skin = skinData.skin(); - SkinProvider.Cape cape = skinData.cape(); - SkinProvider.SkinGeometry geometry = skinData.geometry(); - - if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) { - PlayerListPacket.Entry updatedEntry = SkinManager.buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry - ); - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - } else { - PlayerSkinPacket packet = new PlayerSkinPacket(); - packet.setUuid(entity.getUuid()); - packet.setOldSkinName(""); - packet.setNewSkinName(skin.getTextureUrl()); - packet.setSkin(getSkin(skin.getTextureUrl(), skin, cape, geometry)); - packet.setTrustedSkin(true); - session.sendUpstreamPacket(packet); - } - } - - private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { - return SerializedSkin.of(skinId, "", geometry.getGeometryName(), - ImageData.of(skin.getSkinData()), Collections.emptyList(), - ImageData.of(cape.getCapeData()), geometry.getGeometryData(), - "", true, false, false, cape.getCapeId(), skinId); - } - @AllArgsConstructor @Getter @Setter diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 730d46908..85a1539bc 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -32,6 +32,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.protocol.bedrock.data.skin.ImageData; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; @@ -67,10 +68,8 @@ public class SkinManager { playerEntity.getUuid(), playerEntity.getUsername(), playerEntity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), + skin, + cape, geometry ); } @@ -79,14 +78,10 @@ public class SkinManager { * With all the information needed, build a Bedrock player entry with translated skin information. */ public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, - String skinId, byte[] skinData, - String capeId, byte[] capeData, + SkinProvider.Skin skin, + SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { - SerializedSkin serializedSkin = SerializedSkin.of( - skinId, "", geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), - ImageData.of(capeData), geometry.getGeometryData(), "", true, false, - !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId - ); + SerializedSkin serializedSkin = getSkin(skin.getTextureUrl(), skin, cape, geometry); // This attempts to find the XUID of the player so profile images show up for Xbox accounts String xuid = ""; @@ -116,6 +111,45 @@ public class SkinManager { return entry; } + public static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinProvider.SkinData skinData) { + SkinProvider.Skin skin = skinData.skin(); + SkinProvider.Cape cape = skinData.cape(); + SkinProvider.SkinGeometry geometry = skinData.geometry(); + + if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) { + // TODO is this special behavior needed? + PlayerListPacket.Entry updatedEntry = buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin, + cape, + geometry + ); + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); + } else { + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(getSkin(skin.getTextureUrl(), skin, cape, geometry)); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); + } + } + + private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { + return SerializedSkin.of(skinId, "", geometry.getGeometryName(), + ImageData.of(skin.getSkinData()), Collections.emptyList(), + ImageData.of(cape.getCapeData()), geometry.getGeometryData(), + "", true, false, false, cape.getCapeId(), skinId); + } + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, Consumer skinAndCapeConsumer) { SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> { @@ -128,34 +162,7 @@ public class SkinManager { } if (skinData.geometry() != null) { - SkinProvider.Skin skin = skinData.skin(); - SkinProvider.Cape cape = skinData.cape(); - SkinProvider.SkinGeometry geometry = skinData.geometry(); - - PlayerListPacket.Entry updatedEntry = buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry - ); - - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - - if (!entity.isPlayerList()) { - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); - } + sendSkinPacket(session, entity, skinData); } if (skinAndCapeConsumer != null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java index 4e9f0ca42..1aa9e33d9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoRemoveTranslator.java @@ -45,17 +45,15 @@ public class JavaPlayerInfoRemoveTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, ClientboundPlayerInfoUpdatePacket packet) { - if (!packet.getActions().contains(PlayerListEntryAction.ADD_PLAYER)) { - return; - } + Set actions = packet.getActions(); - PlayerListPacket translate = new PlayerListPacket(); - translate.setAction(PlayerListPacket.Action.ADD); + if (actions.contains(PlayerListEntryAction.ADD_PLAYER)) { + for (PlayerListEntry entry : packet.getEntries()) { + GameProfile profile = entry.getProfile(); + PlayerEntity playerEntity; + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); - for (PlayerListEntry entry : packet.getEntries()) { - GameProfile profile = entry.getProfile(); - PlayerEntity playerEntity; - boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); + GameProfile.Property textures = profile.getProperty("textures"); + String texturesProperty = textures == null ? null : textures.getValue(); - if (self) { - // Entity is ourself - playerEntity = session.getPlayerEntity(); - } else { - playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); - } + if (self) { + // Entity is ourself + playerEntity = session.getPlayerEntity(); + } else { + // It's a new player + playerEntity = new PlayerEntity( + session, + -1, + session.getEntityCache().getNextEntityId().incrementAndGet(), + profile.getId(), + Vector3f.ZERO, + Vector3f.ZERO, + 0, 0, 0, + profile.getName(), + texturesProperty + ); - GameProfile.Property textures = profile.getProperty("textures"); - String texturesProperty = textures == null ? null : textures.getValue(); - - if (playerEntity == null) { - // It's a new player - playerEntity = new PlayerEntity( - session, - -1, - session.getEntityCache().getNextEntityId().incrementAndGet(), - profile.getId(), - Vector3f.ZERO, - Vector3f.ZERO, - 0, 0, 0, - profile.getName(), - texturesProperty - ); - - session.getEntityCache().addPlayerEntity(playerEntity); - } else { + session.getEntityCache().addPlayerEntity(playerEntity); + } playerEntity.setUsername(profile.getName()); playerEntity.setTexturesProperty(texturesProperty); - } - playerEntity.setPlayerList(true); - // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape - // But we need to send other player's entries so they show up in the player list - // without processing their skin information - that'll be processed when they spawn in - if (self) { - SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> - GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); - } else { - playerEntity.setValid(true); - PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - - translate.getEntries().add(playerListEntry); + // We'll send our own PlayerListEntry in requestAndHandleSkinAndCape + // But we need to send other player's entries so they show up in the player list + // without processing their skin information - that'll be processed when they spawn in + if (self) { + SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> + GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); + } else { + playerEntity.setValid(true); + } } } - if (!translate.getEntries().isEmpty()) { - session.sendUpstreamPacket(translate); + if (actions.contains(PlayerListEntryAction.UPDATE_LISTED)) { + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + + for (PlayerListEntry entry : packet.getEntries()) { + PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfileId()); + if (entity == null) { + session.getGeyser().getLogger().debug("Ignoring player info update for " + entry.getProfileId()); + continue; + } + + if (entry.isListed()) { + PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, entity); + toAdd.add(playerListEntry); + } else { + toRemove.add(new PlayerListPacket.Entry(entity.getTabListUuid())); + } + } + + if (!toAdd.isEmpty()) { + PlayerListPacket tabListPacket = new PlayerListPacket(); + tabListPacket.setAction(PlayerListPacket.Action.ADD); + tabListPacket.getEntries().addAll(toAdd); + session.sendUpstreamPacket(tabListPacket); + } + if (!toRemove.isEmpty()) { + PlayerListPacket tabListPacket = new PlayerListPacket(); + tabListPacket.setAction(PlayerListPacket.Action.REMOVE); + tabListPacket.getEntries().addAll(toRemove); + session.sendUpstreamPacket(tabListPacket); + } } } } From ad4424d2b656ba6f4acf153e9a9f4f759cf55c7b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 17 Dec 2022 21:05:41 -0500 Subject: [PATCH 05/38] Use server data to pick block on banners --- .../world/GeyserFabricWorldManager.java | 125 +++++++++++++++++- .../manager/GeyserSpigotWorldManager.java | 51 ++++++- .../geysermc/geyser/level/WorldManager.java | 15 ++- .../geyser/session/GeyserSession.java | 11 ++ .../inventory/item/nbt/BannerTranslator.java | 24 ++-- .../BedrockBlockPickRequestTranslator.java | 43 +++++- .../BedrockEntityPickRequestTranslator.java | 6 +- 7 files changed, 254 insertions(+), 21 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java index eb4f61c67..b003a76ba 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -31,12 +31,13 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.*; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.WritableBookItem; import net.minecraft.world.item.WrittenBookItem; +import net.minecraft.world.level.block.entity.BannerBlockEntity; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.LecternBlockEntity; import org.geysermc.geyser.level.GeyserWorldManager; @@ -44,8 +45,10 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; import org.geysermc.geyser.util.BlockEntityUtils; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; public class GeyserFabricWorldManager extends GeyserWorldManager { @@ -127,7 +130,127 @@ public class GeyserFabricWorldManager extends GeyserWorldManager { return Permissions.check(player, permission); } + @Nonnull + @Override + public CompletableFuture getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { + CompletableFuture future = new CompletableFuture<>(); + server.execute(() -> { + ServerPlayer player = getPlayer(session); + if (player == null) { + future.complete(null); + return; + } + + BlockPos pos = new BlockPos(x, y, z); + // Don't create a new block entity if invalid + BlockEntity blockEntity = player.level.getChunkAt(pos).getBlockEntity(pos); + if (blockEntity instanceof BannerBlockEntity banner) { + // Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and* + // the banner might have a custom name, both of which a Java client knows and caches + ItemStack itemStack = banner.getItem(); + var tag = OpenNbtTagVisitor.convert("", itemStack.getOrCreateTag()); + + future.complete(tag); + return; + } + future.complete(null); + }); + return future; + } + private ServerPlayer getPlayer(GeyserSession session) { return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid()); } + + // Future considerations: option to clone; would affect arrays + private static class OpenNbtTagVisitor implements TagVisitor { + private String currentKey; + private final com.github.steveice10.opennbt.tag.builtin.CompoundTag root; + private com.github.steveice10.opennbt.tag.builtin.Tag currentTag; + + OpenNbtTagVisitor(String key) { + root = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(key); + } + + @Override + public void visitString(StringTag stringTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.StringTag(currentKey, stringTag.getAsString()); + } + + @Override + public void visitByte(ByteTag byteTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteTag(currentKey, byteTag.getAsByte()); + } + + @Override + public void visitShort(ShortTag shortTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.ShortTag(currentKey, shortTag.getAsShort()); + } + + @Override + public void visitInt(IntTag intTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.IntTag(currentKey, intTag.getAsInt()); + } + + @Override + public void visitLong(LongTag longTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.LongTag(currentKey, longTag.getAsLong()); + } + + @Override + public void visitFloat(FloatTag floatTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.FloatTag(currentKey, floatTag.getAsFloat()); + } + + @Override + public void visitDouble(DoubleTag doubleTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.DoubleTag(currentKey, doubleTag.getAsDouble()); + } + + @Override + public void visitByteArray(ByteArrayTag byteArrayTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteArrayTag(currentKey, byteArrayTag.getAsByteArray()); + } + + @Override + public void visitIntArray(IntArrayTag intArrayTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.IntArrayTag(currentKey, intArrayTag.getAsIntArray()); + } + + @Override + public void visitLongArray(LongArrayTag longArrayTag) { + currentTag = new com.github.steveice10.opennbt.tag.builtin.LongArrayTag(currentKey, longArrayTag.getAsLongArray()); + } + + @Override + public void visitList(ListTag listTag) { + var newList = new com.github.steveice10.opennbt.tag.builtin.ListTag(currentKey); + for (Tag tag : listTag) { + currentKey = ""; + tag.accept(this); + newList.add(currentTag); + } + currentTag = newList; + } + + @Override + public void visitCompound(CompoundTag compoundTag) { + currentTag = convert(currentKey, compoundTag); + } + + private static com.github.steveice10.opennbt.tag.builtin.CompoundTag convert(String name, CompoundTag compoundTag) { + OpenNbtTagVisitor visitor = new OpenNbtTagVisitor(name); + for (String key : compoundTag.getAllKeys()) { + visitor.currentKey = key; + Tag tag = compoundTag.get(key); + tag.accept(visitor); + visitor.root.put(visitor.currentTag); + } + return visitor.root; + } + + @Override + public void visitEnd(EndTag endTag) { + } + } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 52f29dcfe..cca982cbb 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -25,14 +25,17 @@ package org.geysermc.geyser.platform.spigot.world.manager; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.Lectern; +import org.bukkit.block.*; +import org.bukkit.block.banner.Pattern; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; @@ -43,10 +46,14 @@ import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; +import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator; import org.geysermc.geyser.util.BlockEntityUtils; +import org.jetbrains.annotations.Nullable; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * The base world manager to use when there is no supported NMS revision @@ -173,6 +180,46 @@ public class GeyserSpigotWorldManager extends WorldManager { return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); } + @Nonnull + @Override + public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { + CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>(); + // Paper 1.19.3 complains about async access otherwise. + // java.lang.IllegalStateException: Tile is null, asynchronous access? + Bukkit.getScheduler().runTask(this.plugin, () -> { + Player bukkitPlayer; + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) { + future.complete(null); + return; + } + + Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); + BlockState state = block.getState(); + if (state instanceof Banner banner) { + ListTag list = new ListTag("Patterns"); + for (int i = 0; i < banner.numberOfPatterns(); i++) { + Pattern pattern = banner.getPattern(i); + list.add(BannerTranslator.getJavaPatternTag(pattern.getPattern().getIdentifier(), pattern.getColor().ordinal())); + } + + CompoundTag root = addToBlockEntityTag(list); + + future.complete(root); + return; + } + future.complete(null); + }); + return future; + } + + private CompoundTag addToBlockEntityTag(Tag tag) { + CompoundTag compoundTag = new CompoundTag(""); + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(tag); + compoundTag.put(blockEntityTag); + return compoundTag; + } + /** * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id * to the current one. diff --git a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index e10981f4b..1909915db 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -27,12 +27,15 @@ package org.geysermc.geyser.level; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import org.geysermc.geyser.session.GeyserSession; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import java.util.Locale; +import java.util.concurrent.CompletableFuture; /** * Class that manages or retrieves various information @@ -166,4 +169,14 @@ public abstract class WorldManager { public String[] getBiomeIdentifiers(boolean withTags) { return null; } + + /** + * Used for pick block, so we don't need to cache more data than necessary. + * + * @return expected NBT for this item. + */ + @Nonnull + public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { + return CompletableFuture.completedFuture(null); + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 8f9bc394a..743cc83db 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1073,6 +1073,17 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { closed = true; } + /** + * Moves task to the session event loop if already not in it. Otherwise, the task is automatically ran. + */ + public void ensureInEventLoop(Runnable runnable) { + if (eventLoop.inEventLoop()) { + runnable.run(); + return; + } + executeInEventLoop(runnable); + } + /** * Executes a task and prints a stack trace if an error occurs. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java index 95dd07f22..a69a2cfe9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BannerTranslator.java @@ -56,17 +56,17 @@ public class BannerTranslator extends NbtItemStackTranslator { static { OMINOUS_BANNER_PATTERN = new ListTag("Patterns"); // Construct what an ominous banner is supposed to look like - OMINOUS_BANNER_PATTERN.add(getPatternTag("mr", 9)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("bs", 8)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("cs", 7)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 8)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("ms", 15)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("hh", 8)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("mc", 8)); - OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 15)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("mr", 9)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("bs", 8)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("cs", 7)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("bo", 8)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("ms", 15)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("hh", 8)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("mc", 8)); + OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("bo", 15)); } - private static CompoundTag getPatternTag(String pattern, int color) { + public static CompoundTag getJavaPatternTag(String pattern, int color) { StringTag patternType = new StringTag("Pattern", pattern); IntTag colorTag = new IntTag("Color", color); CompoundTag tag = new CompoundTag(""); @@ -117,11 +117,7 @@ public class BannerTranslator extends NbtItemStackTranslator { * @return The Java edition format pattern nbt */ public static CompoundTag getJavaBannerPattern(NbtMap pattern) { - Map tags = new HashMap<>(); - tags.put("Color", new IntTag("Color", 15 - pattern.getInt("Color"))); - tags.put("Pattern", new StringTag("Pattern", pattern.getString("Pattern"))); - - return new CompoundTag("", tags); + return BannerTranslator.getJavaPatternTag(pattern.getString("Pattern"), 15 - pattern.getInt("Color")); } /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java index 8d7cbe22b..90316a8bd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockPickRequestTranslator.java @@ -25,12 +25,18 @@ package org.geysermc.geyser.translator.protocol.bedrock; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -61,6 +67,41 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator { + if (tag == null) { + pickItem(session, blockMapping); + return; + } + + session.ensureInEventLoop(() -> { + if (addNbtData) { + ListTag lore = new ListTag("Lore"); + lore.add(new StringTag("", "\"(+NBT)\"")); + CompoundTag display = tag.get("display"); + if (display == null) { + display = new CompoundTag("display"); + tag.put(display); + } + display.put(lore); + } + // I don't really like this... I'd rather get an ID from the block mapping I think + ItemMapping mapping = session.getItemMappings().getMapping(blockMapping.getPickItem()); + + ItemStack itemStack = new ItemStack(mapping.getJavaId(), 1, tag); + InventoryUtils.findOrCreateItem(session, itemStack); + }); + }); + return; + } + + pickItem(session, blockMapping); + } + + private void pickItem(GeyserSession session, BlockMapping blockToPick) { + InventoryUtils.findOrCreateItem(session, blockToPick.getPickItem()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java index a9ef65fb5..482d153bb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -45,7 +44,10 @@ public class BedrockEntityPickRequestTranslator extends PacketTranslator Date: Sun, 18 Dec 2022 13:18:06 -0500 Subject: [PATCH 06/38] Support new default Java skins from 1.19.3 --- .../java/org/geysermc/geyser/Constants.java | 1 - .../java/org/geysermc/geyser/GeyserImpl.java | 20 +- .../geyser/skin/FakeHeadProvider.java | 2 +- .../geysermc/geyser/skin/ProvidedSkin.java | 63 ---- .../geysermc/geyser/skin/ProvidedSkins.java | 128 +++++++ .../org/geysermc/geyser/skin/SkinManager.java | 71 ++-- .../geysermc/geyser/skin/SkinProvider.java | 279 +++++++++------ .../geyser/skin/SkullSkinManager.java | 6 +- .../geysermc/geyser/text/MinecraftLocale.java | 279 ++------------- .../JavaPlayerInfoUpdateTranslator.java | 4 - .../org/geysermc/geyser/util/AssetUtils.java | 329 ++++++++++++++++++ .../main/resources/bedrock/skin/skin_alex.png | Bin 2105 -> 0 bytes .../resources/bedrock/skin/skin_steve.png | Bin 2162 -> 0 bytes core/src/main/resources/config.yml | 2 +- 14 files changed, 704 insertions(+), 480 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java create mode 100644 core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java create mode 100644 core/src/main/java/org/geysermc/geyser/util/AssetUtils.java delete mode 100644 core/src/main/resources/bedrock/skin/skin_alex.png delete mode 100644 core/src/main/resources/bedrock/skin/skin_steve.png diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 6a53c37de..46a1cb2ec 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -30,7 +30,6 @@ import java.net.URISyntaxException; public final class Constants { public static final URI GLOBAL_API_WS_URI; - public static final String NTP_SERVER = "time.cloudflare.com"; public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v2/news/"; public static final String NEWS_PROJECT_NAME = "geyser"; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index a10e54f90..91e8e4c52 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -79,6 +79,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.skin.ProvidedSkins; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; @@ -95,6 +96,7 @@ import java.net.UnknownHostException; import java.security.Key; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -195,7 +197,23 @@ public class GeyserImpl implements GeyserApi { EntityDefinitions.init(); ItemTranslator.init(); MessageTranslator.init(); - MinecraftLocale.init(); + + // Download the latest asset list and cache it + AssetUtils.generateAssetCache().whenComplete((aVoid, ex) -> { + if (ex != null) { + return; + } + MinecraftLocale.ensureEN_US(); + String locale = GeyserLocale.getDefaultLocale(); + if (!"en_us".equals(locale)) { + // English will be loaded after assets are downloaded, if necessary + MinecraftLocale.downloadAndLoadLocale(locale); + } + + ProvidedSkins.init(); + + CompletableFuture.runAsync(AssetUtils::downloadAndRunClientJarTasks); + }); startInstance(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 464f53d48..b312f9811 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -63,7 +63,7 @@ public class FakeHeadProvider { SkinProvider.Skin skin = skinData.skin(); SkinProvider.Cape cape = skinData.cape(); - SkinProvider.SkinGeometry geometry = skinData.geometry().getGeometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}") + SkinProvider.SkinGeometry geometry = skinData.geometry().geometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}") ? SkinProvider.WEARING_CUSTOM_SKULL_SLIM : SkinProvider.WEARING_CUSTOM_SKULL; SkinProvider.Skin headSkin = SkinProvider.getOrDefault( diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java deleted file mode 100644 index bb638556d..000000000 --- a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.skin; - -import lombok.Getter; -import org.geysermc.geyser.GeyserImpl; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -public class ProvidedSkin { - @Getter private byte[] skin; - - public ProvidedSkin(String internalUrl) { - try { - BufferedImage image; - try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(internalUrl)) { - image = ImageIO.read(stream); - } - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4); - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - int rgba = image.getRGB(x, y); - outputStream.write((rgba >> 16) & 0xFF); // Red - outputStream.write((rgba >> 8) & 0xFF); // Green - outputStream.write(rgba & 0xFF); // Blue - outputStream.write((rgba >> 24) & 0xFF); // Alpha - } - } - image.flush(); - skin = outputStream.toByteArray(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java new file mode 100644 index 000000000..999df0929 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.skin; + +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.util.AssetUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Objects; +import java.util.UUID; + +public final class ProvidedSkins { + private static final ProvidedSkin[] PROVIDED_SKINS = { + new ProvidedSkin("textures/entity/player/slim/alex.png", true), + new ProvidedSkin("textures/entity/player/slim/ari.png", true), + new ProvidedSkin("textures/entity/player/slim/efe.png", true), + new ProvidedSkin("textures/entity/player/slim/kai.png", true), + new ProvidedSkin("textures/entity/player/slim/makena.png", true), + new ProvidedSkin("textures/entity/player/slim/noor.png", true), + new ProvidedSkin("textures/entity/player/slim/steve.png", true), + new ProvidedSkin("textures/entity/player/slim/sunny.png", true), + new ProvidedSkin("textures/entity/player/slim/zuri.png", true), + new ProvidedSkin("textures/entity/player/wide/alex.png", false), + new ProvidedSkin("textures/entity/player/wide/ari.png", false), + new ProvidedSkin("textures/entity/player/wide/efe.png", false), + new ProvidedSkin("textures/entity/player/wide/kai.png", false), + new ProvidedSkin("textures/entity/player/wide/makena.png", false), + new ProvidedSkin("textures/entity/player/wide/noor.png", false), + new ProvidedSkin("textures/entity/player/wide/steve.png", false), + new ProvidedSkin("textures/entity/player/wide/sunny.png", false), + new ProvidedSkin("textures/entity/player/wide/zuri.png", false) + }; + + public static ProvidedSkin getDefaultPlayerSkin(UUID uuid) { + return PROVIDED_SKINS[Math.floorMod(uuid.hashCode(), PROVIDED_SKINS.length)]; + } + + private ProvidedSkins() { + } + + public static final class ProvidedSkin { + private SkinProvider.Skin data; + private final boolean slim; + + ProvidedSkin(String asset, boolean slim) { + this.slim = slim; + + Path folder = GeyserImpl.getInstance().getBootstrap().getConfigFolder() + .resolve("cache") + .resolve("default_player_skins") + .resolve(slim ? "slim" : "wide"); + String assetName = asset.substring(asset.lastIndexOf('/') + 1); + + File location = folder.resolve(assetName).toFile(); + AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset, + (stream) -> AssetUtils.saveFile(location, stream), + () -> { + try { + // TODO lazy initialize? + BufferedImage image; + try (InputStream stream = new FileInputStream(location)) { + image = ImageIO.read(stream); + } + + byte[] byteData = SkinProvider.bufferedImageToImageData(image); + image.flush(); + + String identifier = "geysermc:" + assetName + "_" + (slim ? "slim" : "wide"); + this.data = new SkinProvider.Skin(-1, identifier, byteData); + } catch (IOException e) { + e.printStackTrace(); + } + })); + } + + public SkinProvider.Skin getData() { + // Fall back to the default skin if we can't load our skins, or it's not loaded yet. + return Objects.requireNonNullElse(data, SkinProvider.EMPTY_SKIN); + } + + public boolean isSlim() { + return slim; + } + } + + public static void init() { + // no-op + } + + static { + Path folder = GeyserImpl.getInstance().getBootstrap().getConfigFolder() + .resolve("cache") + .resolve("default_player_skins"); + folder.toFile().mkdirs(); + // Two directories since there are two skins for each model: one slim, one wide + folder.resolve("slim").toFile().mkdir(); + folder.resolve("wide").toFile().mkdir(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 85a1539bc..800b71c96 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -34,7 +34,6 @@ import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.BedrockClientData; @@ -54,13 +53,30 @@ public class SkinManager { * Builds a Bedrock player list entry from our existing, cached Bedrock skin information */ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { + // First: see if we have the cached skin texture ID. GameProfileData data = GameProfileData.from(playerEntity); - SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl()); - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + SkinProvider.Skin skin = null; + SkinProvider.Cape cape = null; + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.WIDE; + if (data != null) { + // GameProfileData is not null = server provided us with textures data to work with. + skin = SkinProvider.getCachedSkin(data.skinUrl()); + cape = SkinProvider.getCachedCape(data.capeUrl()); + geometry = data.isAlex() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; + } - SkinProvider.Skin skin = SkinProvider.getCachedSkin(data.skinUrl()); - if (skin == null) { - skin = SkinProvider.EMPTY_SKIN; + if (skin == null || cape == null) { + // The server either didn't have a texture to send, or we didn't have the texture ID cached. + // Let's see if this player is a Bedrock player, and if so, let's pull their skin. + // Otherwise, grab the default player skin + SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity); + if (skin == null) { + skin = fallbackSkinData.skin(); + geometry = fallbackSkinData.geometry(); + } + if (cape == null) { + cape = fallbackSkinData.cape(); + } } return buildEntryManually( @@ -144,10 +160,10 @@ public class SkinManager { } private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { - return SerializedSkin.of(skinId, "", geometry.getGeometryName(), + return SerializedSkin.of(skinId, "", geometry.geometryName(), ImageData.of(skin.getSkinData()), Collections.emptyList(), - ImageData.of(cape.getCapeData()), geometry.getGeometryData(), - "", true, false, false, cape.getCapeId(), skinId); + ImageData.of(cape.capeData()), geometry.geometryData(), + "", true, false, false, cape.capeId(), skinId); } public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, @@ -193,7 +209,7 @@ public class SkinManager { } if (!clientData.getCapeId().equals("")) { - SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); + SkinProvider.storeBedrockCape(clientData.getCapeId(), capeBytes); } } catch (Exception e) { throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); @@ -238,26 +254,21 @@ public class SkinManager { * @param entity entity to build the GameProfileData from * @return The built GameProfileData */ - public static GameProfileData from(PlayerEntity entity) { + public static @Nullable GameProfileData from(PlayerEntity entity) { try { String texturesProperty = entity.getTexturesProperty(); if (texturesProperty == null) { // Likely offline mode - return loadBedrockOrOfflineSkin(entity); - } - GameProfileData data = loadFromJson(texturesProperty); - if (data != null) { - return data; - } else { - return loadBedrockOrOfflineSkin(entity); + return null; } + return loadFromJson(texturesProperty); } catch (IOException exception) { GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { exception.printStackTrace(); } - return loadBedrockOrOfflineSkin(entity); + return null; } } @@ -286,27 +297,5 @@ public class SkinManager { return new GameProfileData(skinUrl, capeUrl, isAlex); } - - /** - * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this - * is a Bedrock player. - */ - private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) { - // Fallback to the offline mode of working it out - UUID uuid = entity.getUuid(); - boolean isAlex = (Math.abs(uuid.hashCode() % 2) == 1); - - String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); - String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); - if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { - GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); - - if (session != null) { - skinUrl = session.getClientData().getSkinId(); - capeUrl = session.getClientData().getCapeId(); - } - } - return new GameProfileData(skinUrl, capeUrl, isAlex); - } } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 43cf30b47..61f24ac1e 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -26,22 +26,25 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import it.unimi.dsi.fastutil.bytes.ByteArrays; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; @@ -57,28 +60,28 @@ import java.util.concurrent.*; import java.util.function.Predicate; public class SkinProvider { - public static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserImpl.getInstance().getConfig().isAllowThirdPartyCapes(); + private static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserImpl.getInstance().getConfig().isAllowThirdPartyCapes(); static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); - public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); - public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN); - public static final byte[] ALEX_SKIN = new ProvidedSkin("bedrock/skin/skin_alex.png").getSkin(); - public static final Skin EMPTY_SKIN_ALEX = new Skin(-1, "alex", ALEX_SKIN); - private static final Map permanentSkins = new HashMap<>() {{ - put("steve", EMPTY_SKIN); - put("alex", EMPTY_SKIN_ALEX); - }}; - private static final Cache cachedSkins = CacheBuilder.newBuilder() + static final Skin EMPTY_SKIN; + static final Cape EMPTY_CAPE = new Cape("", "no-cape", ByteArrays.EMPTY_ARRAY, -1, true); + + private static final Cache CACHED_JAVA_CAPES = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + private static final Cache CACHED_JAVA_SKINS = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) .build(); - private static final Map> requestedSkins = new ConcurrentHashMap<>(); - - public static final Cape EMPTY_CAPE = new Cape("", "no-cape", new byte[0], -1, true); - private static final Cache cachedCapes = CacheBuilder.newBuilder() + private static final Cache CACHED_BEDROCK_CAPES = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) .build(); + private static final Cache CACHED_BEDROCK_SKINS = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + private static final Map> requestedCapes = new ConcurrentHashMap<>(); + private static final Map> requestedSkins = new ConcurrentHashMap<>(); private static final Map cachedGeometry = new ConcurrentHashMap<>(); @@ -86,18 +89,36 @@ public class SkinProvider { * Citizens NPCs use UUID version 2, while legitimate Minecraft players use version 4, and * offline mode players use version 3. */ - public static final Predicate IS_NPC = uuid -> uuid.version() == 2; + private static final Predicate IS_NPC = uuid -> uuid.version() == 2; - public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserImpl.getInstance().getConfig().isAllowThirdPartyEars(); - public static final String EARS_GEOMETRY; - public static final String EARS_GEOMETRY_SLIM; - public static final SkinGeometry SKULL_GEOMETRY; - public static final SkinGeometry WEARING_CUSTOM_SKULL; - public static final SkinGeometry WEARING_CUSTOM_SKULL_SLIM; - - public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final boolean ALLOW_THIRD_PARTY_EARS = GeyserImpl.getInstance().getConfig().isAllowThirdPartyEars(); + private static final String EARS_GEOMETRY; + private static final String EARS_GEOMETRY_SLIM; + static final SkinGeometry SKULL_GEOMETRY; + static final SkinGeometry WEARING_CUSTOM_SKULL; + static final SkinGeometry WEARING_CUSTOM_SKULL_SLIM; static { + // Generate the empty texture to use as an emergency fallback + final int pink = -524040; + final int black = -16777216; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(64 * 4 + 64 * 4); + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + int rgba; + if (y > 32) { + rgba = x >= 32 ? pink : black; + } else { + rgba = x >= 32 ? black : pink; + } + outputStream.write((rgba >> 16) & 0xFF); // Red + outputStream.write((rgba >> 8) & 0xFF); // Green + outputStream.write(rgba & 0xFF); // Blue + outputStream.write((rgba >> 24) & 0xFF); // Alpha + } + } + EMPTY_SKIN = new Skin(-1, "geysermc:empty", outputStream.toByteArray()); + /* Load in the normal ears geometry */ EARS_GEOMETRY = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.ears.json"), StandardCharsets.UTF_8); @@ -141,48 +162,104 @@ public class SkinProvider { } } - public static boolean hasCapeCached(String capeUrl) { - return cachedCapes.getIfPresent(capeUrl) != null; + /** + * Search our cached database for an already existing, translated skin of this Java URL. + */ + static Skin getCachedSkin(String skinUrl) { + return CACHED_JAVA_SKINS.getIfPresent(skinUrl); } - public static Skin getCachedSkin(String skinUrl) { - return permanentSkins.getOrDefault(skinUrl, cachedSkins.getIfPresent(skinUrl)); + /** + * If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback. + */ + static SkinData determineFallbackSkinData(PlayerEntity entity) { + Skin skin = null; + Cape cape = null; + SkinGeometry geometry = SkinGeometry.WIDE; + + if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { + // Let's see if this player is a Bedrock player, and if so, let's pull their skin. + UUID uuid = entity.getUuid(); + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); + if (session != null) { + String skinId = session.getClientData().getSkinId(); + skin = CACHED_BEDROCK_SKINS.getIfPresent(skinId); + String capeId = session.getClientData().getCapeId(); + cape = CACHED_BEDROCK_CAPES.getIfPresent(capeId); + geometry = cachedGeometry.getOrDefault(uuid, geometry); + } + } + + if (skin == null) { + // We don't have a skin for the player right now. Fall back to a default. + ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(entity.getUuid()); + skin = providedSkin.getData(); + geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; + } + + if (cape == null) { + cape = EMPTY_CAPE; + } + + return new SkinData(skin, cape, geometry); } - public static Cape getCachedCape(String capeUrl) { - Cape cape = capeUrl != null ? cachedCapes.getIfPresent(capeUrl) : EMPTY_CAPE; - return cape != null ? cape : EMPTY_CAPE; + /** + * Used as a fallback if an official Java cape doesn't exist for this user. + */ + @Nonnull + private static Cape getCachedBedrockCape(UUID uuid) { + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); + if (session != null) { + String capeId = session.getClientData().getCapeId(); + Cape bedrockCape = CACHED_BEDROCK_CAPES.getIfPresent(capeId); + if (bedrockCape != null) { + return bedrockCape; + } + } + return EMPTY_CAPE; } - public static CompletableFuture requestSkinData(PlayerEntity entity) { + @Nullable + static Cape getCachedCape(String capeUrl) { + if (capeUrl == null) { + return null; + } + return CACHED_JAVA_CAPES.getIfPresent(capeUrl); + } + + static CompletableFuture requestSkinData(PlayerEntity entity) { SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); + if (data == null) { + // This player likely does not have a textures property + return CompletableFuture.completedFuture(determineFallbackSkinData(entity)); + } return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) .thenApplyAsync(skinAndCape -> { try { - Skin skin = skinAndCape.getSkin(); - Cape cape = skinAndCape.getCape(); - SkinGeometry geometry = SkinGeometry.getLegacy(data.isAlex()); + Skin skin = skinAndCape.skin(); + Cape cape = skinAndCape.cape(); + SkinGeometry geometry = data.isAlex() ? SkinGeometry.SLIM : SkinGeometry.WIDE; - if (cape.isFailed()) { - cape = getOrDefault(requestBedrockCape(entity.getUuid()), - EMPTY_CAPE, 3); + // Whether we should see if this player has a Bedrock skin we should check for on failure of + // any skin property + boolean checkForBedrock = entity.getUuid().version() != 4; + + if (cape.failed() && checkForBedrock) { + cape = getCachedBedrockCape(entity.getUuid()); } - if (cape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { + if (cape.failed() && ALLOW_THIRD_PARTY_CAPES) { cape = getOrDefault(requestUnofficialCape( cape, entity.getUuid(), entity.getUsername(), false ), EMPTY_CAPE, CapeProvider.VALUES.length * 3); } - geometry = getOrDefault(requestBedrockGeometry( - geometry, entity.getUuid() - ), geometry, 3); - boolean isDeadmau5 = "deadmau5".equals(entity.getUsername()); // Not a bedrock player check for ears - if (geometry.isFailed() && (ALLOW_THIRD_PARTY_EARS || isDeadmau5)) { + if (geometry.failed() && (ALLOW_THIRD_PARTY_EARS || isDeadmau5)) { boolean isEars; // Its deadmau5, gotta support his skin :) @@ -213,26 +290,17 @@ public class SkinProvider { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); } - return new SkinData(skinAndCape.getSkin(), skinAndCape.getCape(), null); + return new SkinData(skinAndCape.skin(), skinAndCape.cape(), null); }); } - public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { + private static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); - String newSkinUrl = skinUrl; - - if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { - GeyserSession session = GeyserImpl.getInstance().connectionByUuid(playerId); - - if (session != null) { - newSkinUrl = session.getClientData().getSkinId(); - } - } CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( - getOrDefault(requestSkin(playerId, newSkinUrl, false), EMPTY_SKIN, 5), + getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); @@ -241,7 +309,7 @@ public class SkinProvider { }, EXECUTOR_SERVICE); } - public static CompletableFuture requestSkin(UUID playerId, String textureUrl, boolean newThread) { + static CompletableFuture requestSkin(UUID playerId, String textureUrl, boolean newThread) { if (textureUrl == null || textureUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_SKIN); CompletableFuture requestedSkin = requestedSkins.get(textureUrl); if (requestedSkin != null) { @@ -249,7 +317,7 @@ public class SkinProvider { return requestedSkin; } - Skin cachedSkin = getCachedSkin(textureUrl); + Skin cachedSkin = CACHED_JAVA_SKINS.getIfPresent(textureUrl); if (cachedSkin != null) { return CompletableFuture.completedFuture(cachedSkin); } @@ -259,23 +327,26 @@ public class SkinProvider { future = CompletableFuture.supplyAsync(() -> supplySkin(playerId, textureUrl), EXECUTOR_SERVICE) .whenCompleteAsync((skin, throwable) -> { skin.updated = true; - cachedSkins.put(textureUrl, skin); + CACHED_JAVA_SKINS.put(textureUrl, skin); requestedSkins.remove(textureUrl); }); requestedSkins.put(textureUrl, future); } else { Skin skin = supplySkin(playerId, textureUrl); future = CompletableFuture.completedFuture(skin); - cachedSkins.put(textureUrl, skin); + CACHED_JAVA_SKINS.put(textureUrl, skin); } return future; } - public static CompletableFuture requestCape(String capeUrl, CapeProvider provider, boolean newThread) { + private static CompletableFuture requestCape(String capeUrl, CapeProvider provider, boolean newThread) { if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); - if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested + CompletableFuture requestedCape = requestedCapes.get(capeUrl); + if (requestedCape != null) { + return requestedCape; + } - Cape cachedCape = cachedCapes.getIfPresent(capeUrl); + Cape cachedCape = CACHED_JAVA_CAPES.getIfPresent(capeUrl); if (cachedCape != null) { return CompletableFuture.completedFuture(cachedCape); } @@ -284,21 +355,21 @@ public class SkinProvider { if (newThread) { future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), EXECUTOR_SERVICE) .whenCompleteAsync((cape, throwable) -> { - cachedCapes.put(capeUrl, cape); + CACHED_JAVA_CAPES.put(capeUrl, cape); requestedCapes.remove(capeUrl); }); requestedCapes.put(capeUrl, future); } else { Cape cape = supplyCape(capeUrl, provider); // blocking future = CompletableFuture.completedFuture(cape); - cachedCapes.put(capeUrl, cape); + CACHED_JAVA_CAPES.put(capeUrl, cape); } return future; } - public static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, + private static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, String username, boolean newThread) { - if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { + if (officialCape.failed() && ALLOW_THIRD_PARTY_CAPES) { for (CapeProvider provider : CapeProvider.VALUES) { if (provider.type != CapeUrlType.USERNAME && IS_NPC.test(playerId)) { continue; @@ -308,7 +379,7 @@ public class SkinProvider { requestCape(provider.getUrlFor(playerId, username), provider, newThread), EMPTY_CAPE, 4 ); - if (!cape1.isFailed()) { + if (!cape1.failed()) { return CompletableFuture.completedFuture(cape1); } } @@ -316,7 +387,7 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialCape); } - public static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) { + private static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) { if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin); CompletableFuture future; @@ -339,7 +410,7 @@ public class SkinProvider { * @param newThread Should we start in a new thread * @return The updated skin with ears */ - public static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { + private static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { for (EarsProvider provider : EarsProvider.VALUES) { if (provider.type != CapeUrlType.USERNAME && IS_NPC.test(playerId)) { continue; @@ -357,30 +428,17 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialSkin); } - public static CompletableFuture requestBedrockCape(UUID playerID) { - Cape bedrockCape = cachedCapes.getIfPresent(playerID.toString() + ".Bedrock"); - if (bedrockCape == null) { - bedrockCape = EMPTY_CAPE; - } - return CompletableFuture.completedFuture(bedrockCape); + static void storeBedrockSkin(UUID playerID, String skinId, byte[] skinData) { + Skin skin = new Skin(playerID, skinId, skinData, System.currentTimeMillis(), true, false); + CACHED_BEDROCK_SKINS.put(skin.getTextureUrl(), skin); } - public static CompletableFuture requestBedrockGeometry(SkinGeometry currentGeometry, UUID playerID) { - SkinGeometry bedrockGeometry = cachedGeometry.getOrDefault(playerID, currentGeometry); - return CompletableFuture.completedFuture(bedrockGeometry); + static void storeBedrockCape(String capeId, byte[] capeData) { + Cape cape = new Cape(capeId, capeId, capeData, System.currentTimeMillis(), false); + CACHED_BEDROCK_CAPES.put(capeId, cape); } - public static void storeBedrockSkin(UUID playerID, String skinID, byte[] skinData) { - Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true, false); - cachedSkins.put(skin.getTextureUrl(), skin); - } - - public static void storeBedrockCape(UUID playerID, byte[] capeData) { - Cape cape = new Cape(playerID.toString() + ".Bedrock", playerID.toString(), capeData, System.currentTimeMillis(), false); - cachedCapes.put(playerID.toString() + ".Bedrock", cape); - } - - public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) { + static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) { SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(geometryData), false); cachedGeometry.put(playerID, geometry); } @@ -391,7 +449,7 @@ public class SkinProvider { * @param skin The skin to cache */ public static void storeEarSkin(Skin skin) { - cachedSkins.put(skin.getTextureUrl(), skin); + CACHED_JAVA_SKINS.put(skin.getTextureUrl(), skin); } /** @@ -400,7 +458,7 @@ public class SkinProvider { * @param playerID The UUID to cache it against * @param isSlim If the player is using an slim base */ - public static void storeEarGeometry(UUID playerID, boolean isSlim) { + private static void storeEarGeometry(UUID playerID, boolean isSlim) { cachedGeometry.put(playerID, SkinGeometry.getEars(isSlim)); } @@ -414,7 +472,7 @@ public class SkinProvider { } private static Cape supplyCape(String capeUrl, CapeProvider provider) { - byte[] cape = EMPTY_CAPE.getCapeData(); + byte[] cape = EMPTY_CAPE.capeData(); try { cape = requestImage(capeUrl, provider); } catch (Exception ignored) { @@ -604,7 +662,7 @@ public class SkinProvider { } private static BufferedImage readFiveZigCape(String url) throws IOException { - JsonNode element = OBJECT_MAPPER.readTree(WebUtils.getBody(url)); + JsonNode element = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(url)); if (element != null && element.isObject()) { JsonNode capeElement = element.get("d"); if (capeElement == null || capeElement.isNull()) return null; @@ -683,13 +741,12 @@ public class SkinProvider { return defaultValue; } - @AllArgsConstructor - @Getter - public static class SkinAndCape { - private final Skin skin; - private final Cape cape; + public record SkinAndCape(Skin skin, Cape cape) { } + /** + * Represents a full package of skin, cape, and geometry. + */ public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) { } @@ -703,29 +760,19 @@ public class SkinProvider { private boolean updated; private boolean ears; - private Skin(long requestedOn, String textureUrl, byte[] skinData) { + Skin(long requestedOn, String textureUrl, byte[] skinData) { this.requestedOn = requestedOn; this.textureUrl = textureUrl; this.skinData = skinData; } } - @AllArgsConstructor - @Getter - public static class Cape { - private final String textureUrl; - private final String capeId; - private final byte[] capeData; - private final long requestedOn; - private final boolean failed; + public record Cape(String textureUrl, String capeId, byte[] capeData, long requestedOn, boolean failed) { } - @AllArgsConstructor - @Getter - public static class SkinGeometry { - private final String geometryName; - private final String geometryData; - private final boolean failed; + public record SkinGeometry(String geometryName, String geometryData, boolean failed) { + public static SkinGeometry WIDE = getLegacy(false); + public static SkinGeometry SLIM = getLegacy(true); /** * Generate generic geometry @@ -733,7 +780,7 @@ public class SkinProvider { * @param isSlim Should it be the alex model * @return The generic geometry object */ - public static SkinGeometry getLegacy(boolean isSlim) { + private static SkinGeometry getLegacy(boolean isSlim) { return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "", true); } @@ -743,7 +790,7 @@ public class SkinProvider { * @param isSlim Should it be the alex model * @return The generated geometry for the ears model */ - public static SkinGeometry getEars(boolean isSlim) { + private static SkinGeometry getEars(boolean isSlim) { return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.ears" + (isSlim ? "Slim" : "") + "\"}}", (isSlim ? EARS_GEOMETRY_SLIM : EARS_GEOMETRY), false); } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 58054e9c5..2759b1408 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -42,9 +42,9 @@ public class SkullSkinManager extends SkinManager { // Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png skinId = skinId + "_skull"; return SerializedSkin.of( - skinId, "", SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), - ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(), - "", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId + skinId, "", SkinProvider.SKULL_GEOMETRY.geometryName(), ImageData.of(skinData), Collections.emptyList(), + ImageData.of(SkinProvider.EMPTY_CAPE.capeData()), SkinProvider.SKULL_GEOMETRY.geometryData(), + "", true, false, false, SkinProvider.EMPTY_CAPE.capeId(), skinId ); } diff --git a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java index 94ad5eead..9b0edd82f 100644 --- a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java @@ -25,91 +25,45 @@ package org.geysermc.geyser.text; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import lombok.Getter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.util.AssetUtils; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; import java.io.*; import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.zip.ZipFile; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; public class MinecraftLocale { public static final Map> LOCALE_MAPPINGS = new HashMap<>(); - private static final Map ASSET_MAP = new HashMap<>(); - - private static VersionDownload clientJarInfo; - static { // Create the locales folder File localesFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile(); //noinspection ResultOfMethodCallIgnored localesFolder.mkdir(); - // Download the latest asset list and cache it - generateAssetCache().whenComplete((aVoid, ex) -> downloadAndLoadLocale(GeyserLocale.getDefaultLocale())); + // FIXME TEMPORARY + try { + Files.delete(localesFolder.toPath().resolve("en_us.hash")); + } catch (IOException ignored) { + } } - /** - * Fetch the latest versions asset cache from Mojang so we can grab the locale files later - */ - private static CompletableFuture generateAssetCache() { - return CompletableFuture.supplyAsync(() -> { - try { - // Get the version manifest from Mojang - VersionManifest versionManifest = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); - - // Get the url for the latest version of the games manifest - String latestInfoURL = ""; - for (Version version : versionManifest.getVersions()) { - if (version.getId().equals(GameProtocol.getJavaCodec().getMinecraftVersion())) { - latestInfoURL = version.getUrl(); - break; + public static void ensureEN_US() { + File localeFile = getFile("en_us"); + AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json", + (stream) -> AssetUtils.saveFile(localeFile, stream), + () -> { + if ("en_us".equals(GeyserLocale.getDefaultLocale())) { + loadLocale("en_us"); } - } - - // Make sure we definitely got a version - if (latestInfoURL.isEmpty()) { - throw new Exception(GeyserLocale.getLocaleStringLog("geyser.locale.fail.latest_version")); - } - - // Get the individual version manifest - VersionInfo versionInfo = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); - - // Get the client jar for use when downloading the en_us locale - GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); - clientJarInfo = versionInfo.getDownloads().get("client"); - GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(clientJarInfo)); - - // Get the assets list - JsonNode assets = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); - - // Put each asset into an array for use later - Iterator> assetIterator = assets.fields(); - while (assetIterator.hasNext()) { - Map.Entry entry = assetIterator.next(); - if (!entry.getKey().startsWith("minecraft/lang/")) { - // No need to cache non-language assets as we don't use them - continue; - } - - Asset asset = GeyserImpl.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); - ASSET_MAP.put(entry.getKey(), asset); - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); - } - return null; - }); + })); } /** @@ -125,7 +79,7 @@ public class MinecraftLocale { } // Check the locale isn't already loaded - if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + if (!AssetUtils.isAssetKnown("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { if (loadLocale(locale)) { GeyserImpl.getInstance().getLogger().debug("Loaded locale locally while not being in asset map: " + locale); } else { @@ -148,33 +102,15 @@ public class MinecraftLocale { * @param locale Locale to download */ private static void downloadLocale(String locale) { - File localeFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); + if (locale.equals("en_us")) { + return; + } + File localeFile = getFile(locale); // Check if we have already downloaded the locale file if (localeFile.exists()) { - String curHash = ""; - String targetHash; - - if (locale.equals("en_us")) { - try { - File hashFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toFile(); - if (hashFile.exists()) { - try (BufferedReader br = new BufferedReader(new FileReader(hashFile))) { - curHash = br.readLine().trim(); - } - } - } catch (IOException ignored) { } - - if (clientJarInfo == null) { - // Likely failed to download - GeyserImpl.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null."); - return; - } - targetHash = clientJarInfo.getSha1(); - } else { - curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); - targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - } + String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); + String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash(); if (!curHash.equals(targetHash)) { GeyserImpl.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); @@ -184,22 +120,19 @@ public class MinecraftLocale { } } - // Create the en_us locale - if (locale.equals("en_us")) { - downloadEN_US(localeFile); - - return; - } - try { // Get the hash and download the locale - String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + String hash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash(); WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error("Unable to download locale file hash", e); } } + private static File getFile(String locale) { + return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); + } + /** * Loads a locale already downloaded, if the file doesn't exist it just logs a warning * @@ -254,51 +187,6 @@ public class MinecraftLocale { } } - /** - * Download then en_us locale by downloading the server jar and extracting it from there. - * - * @param localeFile File to save the locale to - */ - private static void downloadEN_US(File localeFile) { - try { - // Let the user know we are downloading the JAR - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us")); - GeyserImpl.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); - - // Download the smallest JAR (client or server) - Path tmpFilePath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); - WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); - - // Load in the JAR as a zip and extract the file - try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) { - try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) { - try (FileOutputStream outStream = new FileOutputStream(localeFile)) { - - // Write the file to the locale dir - byte[] buf = new byte[fileStream.available()]; - int length; - while ((length = fileStream.read(buf)) != -1) { - outStream.write(buf, 0, length); - } - - // Flush all changes to disk and cleanup - outStream.flush(); - } - } - } - - // Store the latest jar hash - FileUtils.writeFile(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); - - // Delete the nolonger needed client/server jar - Files.delete(tmpFilePath); - - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us.done")); - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.en_us"), e); - } - } - /** * Translate the given language string into the given locale, or falls back to the default locale * @@ -333,111 +221,4 @@ public class MinecraftLocale { } return result.toString(); } - - public static void init() { - // no-op - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class VersionManifest { - @JsonProperty("latest") - private LatestVersion latestVersion; - - @JsonProperty("versions") - private List versions; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class LatestVersion { - @JsonProperty("release") - private String release; - - @JsonProperty("snapshot") - private String snapshot; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class Version { - @JsonProperty("id") - private String id; - - @JsonProperty("type") - private String type; - - @JsonProperty("url") - private String url; - - @JsonProperty("time") - private String time; - - @JsonProperty("releaseTime") - private String releaseTime; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class VersionInfo { - @JsonProperty("id") - private String id; - - @JsonProperty("type") - private String type; - - @JsonProperty("time") - private String time; - - @JsonProperty("releaseTime") - private String releaseTime; - - @JsonProperty("assetIndex") - private AssetIndex assetIndex; - - @JsonProperty("downloads") - private Map downloads; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class VersionDownload { - @JsonProperty("sha1") - private String sha1; - - @JsonProperty("size") - private int size; - - @JsonProperty("url") - private String url; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class AssetIndex { - @JsonProperty("id") - private String id; - - @JsonProperty("sha1") - private String sha1; - - @JsonProperty("size") - private int size; - - @JsonProperty("totalSize") - private int totalSize; - - @JsonProperty("url") - private String url; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - static class Asset { - @JsonProperty("hash") - private String hash; - - @JsonProperty("size") - private int size; - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java index 4b7b3cdf8..2784b1cc4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java @@ -79,10 +79,6 @@ public class JavaPlayerInfoUpdateTranslator extends PacketTranslator GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); diff --git a/core/src/main/java/org/geysermc/geyser/util/AssetUtils.java b/core/src/main/java/org/geysermc/geyser/util/AssetUtils.java new file mode 100644 index 000000000..299e63e0e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/AssetUtils.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.util; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.text.GeyserLocale; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.zip.ZipFile; + +/** + * Implementation note: try to design processes to fail softly if the client jar can't be downloaded, + * either if Mojang is down or internet access to Mojang is spotty. + */ +public final class AssetUtils { + private static final String CLIENT_JAR_HASH_FILE = "client_jar.hash"; + + private static final Map ASSET_MAP = new HashMap<>(); + + private static VersionDownload CLIENT_JAR_INFO; + + private static final Queue CLIENT_JAR_TASKS = new ArrayDeque<>(); + /** + * Download the client jar even if the hash is correct + */ + private static boolean FORCE_DOWNLOAD_JAR = false; + + public static Asset getAsset(String name) { + return ASSET_MAP.get(name); + } + + public static boolean isAssetKnown(String name) { + return ASSET_MAP.containsKey(name); + } + + /** + * Add task to be ran after the client jar is downloaded or found to be cached. + * + * @param required if set to true, the client jar will always be downloaded, even if a pre-existing hash is matched. + * This means an asset or texture is missing. + */ + public static void addTask(boolean required, ClientJarTask task) { + CLIENT_JAR_TASKS.add(task); + FORCE_DOWNLOAD_JAR |= required; + } + + /** + * Fetch the latest versions asset cache from Mojang so we can grab the locale files later + */ + public static CompletableFuture generateAssetCache() { + return CompletableFuture.supplyAsync(() -> { + try { + // Get the version manifest from Mojang + VersionManifest versionManifest = GeyserImpl.JSON_MAPPER.readValue( + WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + + // Get the url for the latest version of the games manifest + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(GameProtocol.getJavaCodec().getMinecraftVersion())) { + latestInfoURL = version.getUrl(); + break; + } + } + + // Make sure we definitely got a version + if (latestInfoURL.isEmpty()) { + throw new Exception(GeyserLocale.getLocaleStringLog("geyser.locale.fail.latest_version")); + } + + // Get the individual version manifest + VersionInfo versionInfo = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + // Get the client jar for use when downloading the en_us locale + GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); + CLIENT_JAR_INFO = versionInfo.getDownloads().get("client"); + GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(CLIENT_JAR_INFO)); + + // Get the assets list + JsonNode assets = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + // Put each asset into an array for use later + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + if (!entry.getKey().startsWith("minecraft/lang/")) { + // No need to cache non-language assets as we don't use them + continue; + } + + Asset asset = GeyserImpl.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); + } + return null; + }); + } + + public static void downloadAndRunClientJarTasks() { + if (CLIENT_JAR_INFO == null) { + // Likely failed to download + GeyserImpl.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null."); + return; + } + + if (!FORCE_DOWNLOAD_JAR) { // Don't bother checking the hash if we need to download new files anyway. + String curHash = null; + try { + File hashFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve(CLIENT_JAR_HASH_FILE).toFile(); + if (hashFile.exists()) { + try (BufferedReader br = new BufferedReader(new FileReader(hashFile))) { + curHash = br.readLine().trim(); + } + } + } catch (IOException ignored) { } + String targetHash = CLIENT_JAR_INFO.getSha1(); + if (targetHash.equals(curHash)) { + // Just run all tasks - no new download required + ClientJarTask task; + while ((task = CLIENT_JAR_TASKS.poll()) != null) { + task.whenDone.run(); + } + return; + } + } + + try { + // Let the user know we are downloading the JAR + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us")); + GeyserImpl.getInstance().getLogger().debug("Download URL: " + CLIENT_JAR_INFO.getUrl()); + + Path tmpFilePath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); + WebUtils.downloadFile(CLIENT_JAR_INFO.getUrl(), tmpFilePath.toString()); + + // Load in the JAR as a zip and extract the files + try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) { + ClientJarTask task; + while ((task = CLIENT_JAR_TASKS.poll()) != null) { + try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry(task.asset))) { + task.ifNewDownload.accept(fileStream); + task.whenDone.run(); + } + } + } + + // Store the latest jar hash + Path cache = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache"); + Files.createDirectories(cache); + FileUtils.writeFile(cache.resolve(CLIENT_JAR_HASH_FILE).toString(), CLIENT_JAR_INFO.getSha1().toCharArray()); + + // Delete the nolonger needed client/server jar + Files.delete(tmpFilePath); + + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us.done")); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.en_us"), e); + } + } + + public static void saveFile(File location, InputStream fileStream) throws IOException { + try (FileOutputStream outStream = new FileOutputStream(location)) { + + // Write the file to the locale dir + byte[] buf = new byte[fileStream.available()]; + int length; + while ((length = fileStream.read(buf)) != -1) { + outStream.write(buf, 0, length); + } + + // Flush all changes to disk and cleanup + outStream.flush(); + } + } + + /** + * A process that requires we download the client jar. + * Designed to accommodate Geyser updates that require more assets from the jar. + */ + public record ClientJarTask(String asset, InputStreamConsumer ifNewDownload, Runnable whenDone) { + } + + @FunctionalInterface + public interface InputStreamConsumer { + void accept(InputStream stream) throws IOException; + } + + /* Classes that map to JSON files served by Mojang */ + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionManifest { + @JsonProperty("latest") + private LatestVersion latestVersion; + + @JsonProperty("versions") + private List versions; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class LatestVersion { + @JsonProperty("release") + private String release; + + @JsonProperty("snapshot") + private String snapshot; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class Version { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionInfo { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + + @JsonProperty("assetIndex") + private AssetIndex assetIndex; + + @JsonProperty("downloads") + private Map downloads; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionDownload { + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("url") + private String url; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class AssetIndex { + @JsonProperty("id") + private String id; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("url") + private String url; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + public static class Asset { + @JsonProperty("hash") + private String hash; + + @JsonProperty("size") + private int size; + } + + private AssetUtils() { + } +} diff --git a/core/src/main/resources/bedrock/skin/skin_alex.png b/core/src/main/resources/bedrock/skin/skin_alex.png deleted file mode 100644 index ffd8e0719a1a4fac1f58895ed3a30130d3aa4b46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2105 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 diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 502441560..ac5e76cd1 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -111,7 +111,7 @@ debug-mode: false # Allow third party capes to be visible. Currently allowing: # OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes -allow-third-party-capes: true +allow-third-party-capes: false # Allow third party deadmau5 ears to be visible. Currently allowing: # MinecraftCapes From 98069cff83c923d808b5fe8b54f0a2458a9a37e7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 00:35:03 -0500 Subject: [PATCH 07/38] Fix certain sounds not correctly playing Fixes #3463 --- .../org/geysermc/geyser/util/SoundUtils.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java index b80a9afe7..ccfd658ce 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.util; +import com.github.steveice10.mc.protocol.data.game.level.sound.BuiltinSound; import com.github.steveice10.mc.protocol.data.game.level.sound.Sound; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -65,10 +66,7 @@ public final class SoundUtils { * @return a Bedrock sound */ public static String translatePlaySound(String javaIdentifier) { - // Drop the Minecraft namespace if applicable - if (javaIdentifier.startsWith("minecraft:")) { - javaIdentifier = javaIdentifier.substring("minecraft:".length()); - } + javaIdentifier = trim(javaIdentifier); SoundMapping soundMapping = Registries.SOUNDS.get(javaIdentifier); if (soundMapping == null || soundMapping.getPlaysound() == null) { @@ -79,6 +77,23 @@ public final class SoundUtils { return soundMapping.getPlaysound(); } + private static String trim(String identifier) { + // Drop the Minecraft namespace if applicable + if (identifier.startsWith("minecraft:")) { + return identifier.substring("minecraft:".length()); + } + return identifier; + } + + private static void playSound(GeyserSession session, String bedrockName, Vector3f position, float volume, float pitch) { + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound(bedrockName); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(volume); + playSoundPacket.setPitch(pitch); + session.sendUpstreamPacket(playSoundPacket); + } + /** * Translates and plays a Java Builtin Sound for a Bedrock client * @@ -88,22 +103,24 @@ public final class SoundUtils { * @param pitch the pitch */ public static void playSound(GeyserSession session, Sound javaSound, Vector3f position, float volume, float pitch) { - String packetSound = javaSound.getName(); + String packetSound; + if (!(javaSound instanceof BuiltinSound)) { + // Identifier needs trimmed probably. + packetSound = translatePlaySound(javaSound.getName()); + } else { + packetSound = javaSound.getName(); + } SoundMapping soundMapping = Registries.SOUNDS.get(packetSound); if (soundMapping == null) { - session.getGeyser().getLogger().debug("[Builtin] Sound mapping for " + packetSound + " not found"); + session.getGeyser().getLogger().debug("[Builtin] Sound mapping for " + packetSound + " not found; assuming custom."); + playSound(session, packetSound, position, volume, pitch); return; } if (soundMapping.getPlaysound() != null) { // We always prefer the PlaySound mapping because we can control volume and pitch - PlaySoundPacket playSoundPacket = new PlaySoundPacket(); - playSoundPacket.setSound(soundMapping.getPlaysound()); - playSoundPacket.setPosition(position); - playSoundPacket.setVolume(volume); - playSoundPacket.setPitch(pitch); - session.sendUpstreamPacket(playSoundPacket); + playSound(session, soundMapping.getPlaysound(), position, volume, pitch); return; } From fcd5fe1341ee1346d1576deac929e4483886c990 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 00:37:48 -0500 Subject: [PATCH 08/38] Wrong method call on previous commit --- core/src/main/java/org/geysermc/geyser/util/SoundUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java index ccfd658ce..fd694f53e 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java @@ -106,7 +106,7 @@ public final class SoundUtils { String packetSound; if (!(javaSound instanceof BuiltinSound)) { // Identifier needs trimmed probably. - packetSound = translatePlaySound(javaSound.getName()); + packetSound = trim(javaSound.getName()); } else { packetSound = javaSound.getName(); } From 03390b99e8240dc96a12414a016812b5e34de4dd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:18:49 -0500 Subject: [PATCH 09/38] Fix black cats not appearing correctly --- .../entity/type/living/animal/tameable/CatEntity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java index f700f6951..cca62f543 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java @@ -50,6 +50,13 @@ public class CatEntity extends TameableEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Default value (minecraft:black). + dirtyMetadata.put(EntityData.VARIANT, 1); + } + @Override public void updateRotation(float yaw, float pitch, boolean isOnGround) { moveRelative(0, 0, 0, yaw, pitch, yaw, isOnGround); From c48cb2a4a8779194e1a17bb6a556978ff4cea621 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 22 Dec 2022 13:19:46 -0500 Subject: [PATCH 10/38] Remount players in their vehicle if they're not supposed to leave --- .../geysermc/geyser/entity/type/Entity.java | 2 -- .../geyser/session/GeyserSession.java | 8 +++++-- .../player/BedrockInteractTranslator.java | 21 +++++++++++++++++++ .../player/JavaPlayerPositionTranslator.java | 7 +++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 663dd3c33..ed009bf8a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -44,8 +44,6 @@ import lombok.Setter; import net.kyori.adventure.text.Component; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; -import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.EntityUtils; diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index d91c277a6..72697a85c 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -62,7 +62,6 @@ import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpClientSession; import com.github.steveice10.packetlib.tcp.TcpSession; -import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.*; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -135,7 +134,6 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; -import org.geysermc.geyser.util.MathUtils; import java.net.ConnectException; import java.net.InetSocketAddress; @@ -539,6 +537,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private ScheduledFuture lookBackScheduledFuture = null; + /** + * Used to return players back to their vehicles if the server doesn't want them unmounting. + */ + @Setter + private ScheduledFuture mountVehicleScheduledFuture = null; + private MinecraftProtocol protocol; public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java index fed8f5ce6..22a895465 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java @@ -32,15 +32,19 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import java.util.concurrent.TimeUnit; + @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { @@ -73,6 +77,23 @@ public class BedrockInteractTranslator extends PacketTranslator case LEAVE_VEHICLE: ServerboundPlayerCommandPacket sneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING); session.sendDownstreamPacket(sneakPacket); + + Entity currentVehicle = session.getPlayerEntity().getVehicle(); + session.setMountVehicleScheduledFuture(session.scheduleInEventLoop(() -> { + if (session.getPlayerEntity().getVehicle() == null) { + return; + } + + long vehicleBedrockId = currentVehicle.getGeyserId(); + if (session.getPlayerEntity().getVehicle().getGeyserId() == vehicleBedrockId) { + // The Bedrock client, as of 1.19.51, dismounts on its end. The server may not agree with this. + // If the server doesn't agree with our dismount (sends a packet saying we dismounted), + // then remount the player. + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(vehicleBedrockId, session.getPlayerEntity().getGeyserId(), EntityLinkData.Type.PASSENGER, true, false)); + session.sendUpstreamPacket(linkPacket); + } + }, 1, TimeUnit.SECONDS)); break; case MOUSEOVER: // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 0652792ef..520443ae7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -114,6 +114,13 @@ public class JavaPlayerPositionTranslator extends PacketTranslator 1); entity.updateBedrockMetadata(); + + if (session.getMountVehicleScheduledFuture() != null) { + // Cancel this task as it is now unnecessary. + // Note that this isn't present in JavaSetPassengersTranslator as that code is not called for players + // as of Java 1.19.3, but the scheduled future checks for the vehicle being null anyway. + session.getMountVehicleScheduledFuture().cancel(false); + } } // If coordinates are relative, then add to the existing coordinate From 6485200c1ffd47a69bd041290cb75668f4269394 Mon Sep 17 00:00:00 2001 From: David Choo <4722249+davchoo@users.noreply.github.com> Date: Fri, 23 Dec 2022 19:26:37 -0500 Subject: [PATCH 11/38] Fix visual glitch with blocks attached to extending pistons in 1.19.50 (#3475) --- .../level/block/entity/PistonBlockEntity.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java index 28e30d6be..c52689014 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java @@ -42,6 +42,7 @@ import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.PistonCache; @@ -621,8 +622,10 @@ public class PistonBlockEntity { Vector3i movement = getMovement(); attachedBlocks.forEach((blockPos, javaId) -> { blockPos = blockPos.add(movement); - // Send a final block entity packet to detach blocks - BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos); + if (!GameProtocol.supports1_19_50(session)) { + // Send a final block entity packet to detach blocks for clients older than 1.19.50 + BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos); + } // Don't place blocks that collide with the player if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { ChunkUtils.updateBlock(session, javaId, blockPos); @@ -739,8 +742,8 @@ public class PistonBlockEntity { .putFloat("LastProgress", lastProgress) .putByte("NewState", getState()) .putByte("State", getState()) - .putByte("Sticky", (byte) (sticky ? 1 : 0)) - .putByte("isMovable", (byte) 0) + .putBoolean("Sticky", sticky) + .putBoolean("isMovable", false) .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()); @@ -762,8 +765,8 @@ public class PistonBlockEntity { .putFloat("LastProgress", extended ? 1.0f : 0.0f) .putByte("NewState", (byte) (extended ? 2 : 0)) .putByte("State", (byte) (extended ? 2 : 0)) - .putByte("Sticky", (byte) (sticky ? 1 : 0)) - .putByte("isMovable", (byte) 0) + .putBoolean("Sticky", sticky) + .putBoolean("isMovable", false) .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()); @@ -783,8 +786,9 @@ public class PistonBlockEntity { NbtMap movingBlock = session.getBlockMappings().getBedrockBlockStates().get(session.getBlockMappings().getBedrockBlockId(javaId)); NbtMapBuilder builder = NbtMap.builder() .putString("id", "MovingBlock") + .putBoolean("expanding", action == PistonValueType.PUSHING) .putCompound("movingBlock", movingBlock) - .putByte("isMovable", (byte) 1) + .putBoolean("isMovable", true) .putInt("pistonPosX", pistonPosition.getX()) .putInt("pistonPosY", pistonPosition.getY()) .putInt("pistonPosZ", pistonPosition.getZ()) From 566b2635c0f5556a9077d8d9a63dc6481c44a7d7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 30 Dec 2022 16:24:16 -0500 Subject: [PATCH 12/38] Explain why Bedrock players can't chat with enforce-secure-profile --- .../java/org/geysermc/geyser/GeyserImpl.java | 7 ++- .../geyser/session/cache/WorldCache.java | 13 ++++++ .../bedrock/BedrockTextTranslator.java | 12 ++++- .../java/JavaServerDataTranslator.java | 44 +++++++++++++++++++ .../player/JavaPlayerPositionTranslator.java | 3 +- .../entity/spawn/JavaAddEntityTranslator.java | 3 +- core/src/main/resources/languages | 2 +- 7 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaServerDataTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index a10e54f90..982ce6fef 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -223,7 +223,10 @@ public class GeyserImpl implements GeyserApi { logger.info(message); if (platformType == PlatformType.STANDALONE) { - logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); + if (config.getRemote().authType() != AuthType.FLOODGATE) { + // If the auth-type is Floodgate, then this Geyser instance is probably owned by the Java server + logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); + } } else if (config.getRemote().authType() == AuthType.FLOODGATE) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } @@ -329,7 +332,7 @@ public class GeyserImpl implements GeyserApi { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); cipher.init(key); - logger.debug(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + logger.debug("Loaded Floodgate key!"); // Note: this is positioned after the bind so the skin uploader doesn't try to run if Geyser fails // to load successfully. Spigot complains about class loader if the plugin is disabled. skinUploader = new FloodgateSkinUploader(this).start(); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index b3d0518b3..3eaabb399 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -33,6 +33,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntMaps; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; import lombok.Setter; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; @@ -61,6 +63,17 @@ public final class WorldCache { private int currentSequence; private final Object2IntMap unverifiedPredictions = new Object2IntOpenHashMap<>(1); + /** + *
    + *
  • NOT_SET = not yet triggered
  • + *
  • FALSE = enforce-secure-profile is true but player hasn't chatted yet
  • + *
  • TRUE = enforce-secure-profile is enabled, and player has chatted and they have seen our message.
  • + *
+ */ + @Getter + @Setter + private @NonNull TriState chatWarningSent = TriState.NOT_SET; + public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java index 00eb95455..0dedc9b1b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -26,8 +26,9 @@ package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.TextPacket; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -48,6 +49,15 @@ public class BedrockTextTranslator extends PacketTranslator { return; } + if (session.getWorldCache().getChatWarningSent() == TriState.FALSE) { + if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) { + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale())); + session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat")); + } + // Never send this message again for this session. + session.getWorldCache().setChatWarningSent(TriState.TRUE); + } + session.sendChat(message); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaServerDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaServerDataTranslator.java new file mode 100644 index 000000000..9484b99d5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaServerDataTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundServerDataPacket; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = ClientboundServerDataPacket.class) +public class JavaServerDataTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundServerDataPacket packet) { + // We only want to warn about chat maybe not working once + if (packet.isEnforcesSecureChat() && session.getWorldCache().getChatWarningSent() == TriState.NOT_SET) { + session.getWorldCache().setChatWarningSent(TriState.FALSE); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 2d2d7279f..a55e49f70 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -40,7 +40,6 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.TeleportCache; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.ChunkUtils; @@ -97,7 +96,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator definition = Registries.ENTITY_DEFINITIONS.get(packet.getType()); if (definition == null) { - session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.entity.type_null", packet.getType())); + session.getGeyser().getLogger().warning("Could not find an entity definition with type " + packet.getType()); return; } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index a9cf5999a..f6685c4cc 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit a9cf5999af605902b18dd5c77d3562481f8d7f3d +Subproject commit f6685c4ccc6e77b07402d45cb41213559004b7d6 From 3437fc98d8db1ae1172295c21e5c97f210d9e803 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:34:43 -0500 Subject: [PATCH 13/38] Fix ageable entity scales not applying by default Fixes #3486 --- .../geysermc/geyser/entity/type/living/AgeableEntity.java | 6 ++++++ .../geyser/entity/type/living/animal/RabbitEntity.java | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java index 2d1de8ed2..5e604fa2b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java @@ -40,6 +40,12 @@ public class AgeableEntity extends CreatureEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + @Override + protected void initializeMetadata() { + // Required as of 1.19.3 Java + dirtyMetadata.put(EntityData.SCALE, getAdultSize()); + } + public void setBaby(BooleanEntityMetadata entityMetadata) { boolean isBaby = entityMetadata.getPrimitiveValue(); dirtyMetadata.put(EntityData.SCALE, isBaby ? getBabySize() : getAdultSize()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java index c49c9beb3..afa2530d3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -42,11 +41,6 @@ public class RabbitEntity extends AnimalEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void setBaby(BooleanEntityMetadata entityMetadata) { - super.setBaby(entityMetadata); - } - public void setRabbitVariant(IntEntityMetadata entityMetadata) { int variant = entityMetadata.getPrimitiveValue(); From 1b4be6135a1cc0fbaa587d5b37c01490b15b9149 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:44:30 -0500 Subject: [PATCH 14/38] Let's call super too --- .../org/geysermc/geyser/entity/type/living/AgeableEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java index 5e604fa2b..82e43b44d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java @@ -42,6 +42,7 @@ public class AgeableEntity extends CreatureEntity { @Override protected void initializeMetadata() { + super.initializeMetadata(); // Required as of 1.19.3 Java dirtyMetadata.put(EntityData.SCALE, getAdultSize()); } From b93b35b4328a776e031af015a5a1e6f8725cd0cc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:35:16 -0500 Subject: [PATCH 15/38] Should probably call super on this too --- .../geysermc/geyser/entity/type/CommandBlockMinecartEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java index 251eb98a0..d610c9261 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java @@ -46,6 +46,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @Override protected void initializeMetadata() { + super.initializeMetadata(); // Required, or else the GUI will not open dirtyMetadata.put(EntityData.CONTAINER_TYPE, (byte) 16); dirtyMetadata.put(EntityData.CONTAINER_BASE_SIZE, 1); From 402b3403bab09b9dcdae40dfb080fd033360e0f4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:12:22 -0500 Subject: [PATCH 16/38] Velocity: Fix disabling compression when there is no compression to disable --- .../GeyserVelocityCompressionDisabler.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java index 760b68c31..e787e7355 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java @@ -25,28 +25,32 @@ package org.geysermc.geyser.platform.velocity; +import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import org.geysermc.geyser.GeyserImpl; import java.lang.reflect.Method; -public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAdapter { +public class GeyserVelocityCompressionDisabler extends ChannelDuplexHandler { static final boolean ENABLED; private static final Class COMPRESSION_PACKET_CLASS; private static final Class LOGIN_SUCCESS_PACKET_CLASS; + private static final Object COMPRESSION_ENABLED_EVENT; private static final Method SET_COMPRESSION_METHOD; static { boolean enabled = false; Class compressionPacketClass = null; Class loginSuccessPacketClass = null; + Object compressionEnabledEvent = null; Method setCompressionMethod = null; try { compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression"); loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess"); + compressionEnabledEvent = Class.forName("com.velocitypowered.proxy.protocol.VelocityConnectionEvent") + .getDeclaredField("COMPRESSION_ENABLED").get(null); setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection") .getMethod("setCompressionThreshold", int.class); enabled = true; @@ -57,6 +61,7 @@ public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAda ENABLED = enabled; COMPRESSION_PACKET_CLASS = compressionPacketClass; LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass; + COMPRESSION_ENABLED_EVENT = compressionEnabledEvent; SET_COMPRESSION_METHOD = setCompressionMethod; } @@ -72,13 +77,23 @@ public class GeyserVelocityCompressionDisabler extends ChannelOutboundHandlerAda if (!COMPRESSION_PACKET_CLASS.isAssignableFrom(msgClass)) { if (LOGIN_SUCCESS_PACKET_CLASS.isAssignableFrom(msgClass)) { // We're past the point that compression can be enabled - // Invoke the method as it calls a Netty event and handles removing cleaner than we could - Object minecraftConnection = ctx.pipeline().get("handler"); - SET_COMPRESSION_METHOD.invoke(minecraftConnection, -1); ctx.pipeline().remove(this); } super.write(ctx, msg, promise); } } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt != COMPRESSION_ENABLED_EVENT) { + super.userEventTriggered(ctx, evt); + return; + } + + // Invoke the method as it calls a Netty event and handles removing cleaner than we could + Object minecraftConnection = ctx.pipeline().get("handler"); + SET_COMPRESSION_METHOD.invoke(minecraftConnection, -1); + // Do not call super and let the new compression enabled event continue firing + } } From 00df4c26ba13fc8ddf0c92500b414061b592dae4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:51:58 -0500 Subject: [PATCH 17/38] Don't send more than one ServerboundSwingPacket per tick Should address #2875 --- .../geyser/session/GeyserSession.java | 1 - .../bedrock/BedrockAnimateTranslator.java | 21 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 72697a85c..a95706307 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -457,7 +457,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Counts how many ticks have occurred since an arm animation started. * -1 means there is no active arm swing. */ - @Getter(AccessLevel.NONE) private int armAnimationTicks = -1; /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index 670e55785..4905b5647 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -49,11 +49,26 @@ public class BedrockAnimateTranslator extends PacketTranslator { case SWING_ARM -> // Delay so entity damage can be processed first session.scheduleInEventLoop(() -> { + if (session.getArmAnimationTicks() != 0) { + // So, generally, a Java player can only do one *thing* at a time. + // If a player right-clicks, for example, then there's probably only one action associated with + // that right-click that will send a swing. + // The only exception I can think of to this, *maybe*, is a player dropping items + // Bedrock is a little funkier than this - it can send several arm animation packets in the + // same tick, notably with high levels of haste applied. + // Packet limiters do not like this and can crash the player. + // If arm animation ticks is 0, then we just sent an arm swing packet this tick. + // See https://github.com/GeyserMC/Geyser/issues/2875 + // This behavior was last touched on with ViaVersion 4.5.1 (with its packet limiter), Java 1.16.5, + // and Bedrock 1.19.51. + // Note for the future: we should probably largely ignore this packet and instead replicate + // all actions on our end, and send swings where needed. session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); session.activateArmAnimationTicking(); - }, - 25, - TimeUnit.MILLISECONDS + } + }, + 25, + TimeUnit.MILLISECONDS ); // These two might need to be flipped, but my recommendation is getting moving working first case ROW_LEFT -> { From d1afb81a3b4b8f0dc4d8c33db5276233827e8cb1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:10:15 -0500 Subject: [PATCH 18/38] Config: elaborate on show-cooldown --- core/src/main/resources/config.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index f237e32ab..eb5b7e73c 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -4,13 +4,13 @@ # A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition. # # GitHub: https://github.com/GeyserMC/Geyser -# Discord: https://discord.geysermc.org/ +# Discord: https://discord.gg/geysermc # -------------------------------- bedrock: # The IP address that will listen for connections. - # There is no reason to change this unless you want to limit what IPs can connect to your server. - address: 0.0.0.0 + # Generally, you should only uncomment and change this if you want to limit what IPs can connect to your server. + #address: 0.0.0.0 # The port that will listen for connections port: 19132 # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. @@ -117,8 +117,11 @@ allow-third-party-capes: false # MinecraftCapes allow-third-party-ears: false -# Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat -# Can be title, actionbar or false +# Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. +# Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: +# https://cdn.discordapp.com/attachments/613170125696270357/957075682230419466/Screenshot_from_2022-03-25_20-35-08.png +# This can be disabled by going into Bedrock settings under the accessibility tab and setting "Text Background Opacity" to 0 +# This setting can be set to "title", "actionbar" or "false" show-cooldown: title # Controls if coordinates are shown to players. From b5e1ddc3c86bd560e3ebbb8814c7d074c1bd6a69 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 3 Jan 2023 19:28:43 -0500 Subject: [PATCH 19/38] Guess when the client is clicking air on mobile Should address #2113 --- .../geyser/session/GeyserSession.java | 16 +++++-- .../bedrock/BedrockAnimateTranslator.java | 44 ++++++++++--------- .../BedrockLevelSoundEventTranslator.java | 20 +++++++++ 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index a95706307..9ca124393 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -455,7 +455,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { /** * Counts how many ticks have occurred since an arm animation started. - * -1 means there is no active arm swing. + * -1 means there is no active arm swing; -2 means an arm swing will start in a tick. */ private int armAnimationTicks = -1; @@ -1157,7 +1157,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { entity.tick(); } - if (armAnimationTicks != -1) { + if (armAnimationTicks >= 0) { // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the // player's effect status, but the animation can cut short if the duration suddenly decreases // (from suddenly no longer having mining fatigue, for example) @@ -1196,7 +1196,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public void startSneaking() { // Toggle the shield, if there is no ongoing arm animation // This matches Bedrock Edition behavior as of 1.18.12 - if (armAnimationTicks == -1) { + if (armAnimationTicks < 0) { attemptToBlock(); } @@ -1328,6 +1328,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } } + /** + * For https://github.com/GeyserMC/Geyser/issues/2113 and combating arm ticking activating being delayed in + * BedrockAnimateTranslator. + */ + public void armSwingPending() { + if (armAnimationTicks == -1) { + armAnimationTicks = -2; + } + } + /** * Indicates to the client to stop blocking and tells the Java server the same. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index 4905b5647..6122f573b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -46,30 +46,32 @@ public class BedrockAnimateTranslator extends PacketTranslator { } switch (packet.getAction()) { - case SWING_ARM -> + case SWING_ARM -> { + session.armSwingPending(); // Delay so entity damage can be processed first session.scheduleInEventLoop(() -> { - if (session.getArmAnimationTicks() != 0) { - // So, generally, a Java player can only do one *thing* at a time. - // If a player right-clicks, for example, then there's probably only one action associated with - // that right-click that will send a swing. - // The only exception I can think of to this, *maybe*, is a player dropping items - // Bedrock is a little funkier than this - it can send several arm animation packets in the - // same tick, notably with high levels of haste applied. - // Packet limiters do not like this and can crash the player. - // If arm animation ticks is 0, then we just sent an arm swing packet this tick. - // See https://github.com/GeyserMC/Geyser/issues/2875 - // This behavior was last touched on with ViaVersion 4.5.1 (with its packet limiter), Java 1.16.5, - // and Bedrock 1.19.51. - // Note for the future: we should probably largely ignore this packet and instead replicate - // all actions on our end, and send swings where needed. - session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); - session.activateArmAnimationTicking(); - } - }, - 25, - TimeUnit.MILLISECONDS + if (session.getArmAnimationTicks() != 0) { + // So, generally, a Java player can only do one *thing* at a time. + // If a player right-clicks, for example, then there's probably only one action associated with + // that right-click that will send a swing. + // The only exception I can think of to this, *maybe*, is a player dropping items + // Bedrock is a little funkier than this - it can send several arm animation packets in the + // same tick, notably with high levels of haste applied. + // Packet limiters do not like this and can crash the player. + // If arm animation ticks is 0, then we just sent an arm swing packet this tick. + // See https://github.com/GeyserMC/Geyser/issues/2875 + // This behavior was last touched on with ViaVersion 4.5.1 (with its packet limiter), Java 1.16.5, + // and Bedrock 1.19.51. + // Note for the future: we should probably largely ignore this packet and instead replicate + // all actions on our end, and send swings where needed. + session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); + session.activateArmAnimationTicking(); + } + }, + 25, + TimeUnit.MILLISECONDS ); + } // These two might need to be flipped, but my recommendation is getting moving working first case ROW_LEFT -> { // Packet value is a float of how long one has been rowing, so we convert that into a boolean diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java index df8cd07c1..87c03479d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java @@ -25,7 +25,10 @@ package org.geysermc.geyser.translator.protocol.bedrock.world; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -46,5 +49,22 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator Date: Wed, 4 Jan 2023 16:05:12 -0500 Subject: [PATCH 20/38] Fix some instances of chat not appearing --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b595671e..f7f488f17 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20221218.141127-8" +mcprotocollib = "1.19.3-20230104.210231-9" packetlib = "3.0.1" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 3ac931e11b243a8803f5243478e3e2ef9eb295ad Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 7 Jan 2023 14:29:33 +0100 Subject: [PATCH 21/38] Moved the APIs to a dedicated repo They can now be found at https://github.com/GeyserMC/api --- api/base/build.gradle.kts | 7 - .../main/java/org/geysermc/api/Geyser.java | 95 -------- .../java/org/geysermc/api/GeyserApiBase.java | 130 ----------- .../geysermc/api/connection/Connection.java | 121 ---------- .../geysermc/api/util/BedrockPlatform.java | 73 ------ .../java/org/geysermc/api/util/InputMode.java | 49 ---- .../java/org/geysermc/api/util/UiProfile.java | 45 ---- api/geyser/build.gradle.kts | 14 -- .../org/geysermc/geyser/api/GeyserApi.java | 119 ---------- .../geysermc/geyser/api/command/Command.java | 215 ------------------ .../geyser/api/command/CommandExecutor.java | 45 ---- .../geyser/api/command/CommandSource.java | 81 ------- .../api/connection/GeyserConnection.java | 35 --- .../geysermc/geyser/api/event/EventBus.java | 43 ---- .../geyser/api/event/EventRegistrar.java | 47 ---- .../geyser/api/event/EventSubscriber.java | 40 ---- .../geyser/api/event/ExtensionEventBus.java | 41 ---- .../api/event/ExtensionEventSubscriber.java | 32 --- .../api/event/connection/ConnectionEvent.java | 51 ----- .../downstream/ServerDefineCommandsEvent.java | 85 ------- .../lifecycle/GeyserDefineCommandsEvent.java | 57 ----- .../GeyserDefineCustomItemsEvent.java | 76 ------- .../GeyserLoadResourcePacksEvent.java | 40 ---- .../lifecycle/GeyserPostInitializeEvent.java | 41 ---- .../lifecycle/GeyserPreInitializeEvent.java | 41 ---- .../event/lifecycle/GeyserShutdownEvent.java | 38 ---- .../geyser/api/extension/Extension.java | 139 ----------- .../api/extension/ExtensionDescription.java | 106 --------- .../geyser/api/extension/ExtensionLoader.java | 105 --------- .../geyser/api/extension/ExtensionLogger.java | 94 -------- .../api/extension/ExtensionManager.java | 90 -------- .../InvalidDescriptionException.java | 43 ---- .../exception/InvalidExtensionException.java | 43 ---- .../api/item/custom/CustomItemData.java | 109 --------- .../api/item/custom/CustomItemOptions.java | 83 ------- .../api/item/custom/CustomRenderOffsets.java | 51 ----- .../item/custom/NonVanillaCustomItemData.java | 188 --------------- .../geysermc/geyser/api/network/AuthType.java | 61 ----- .../geyser/api/network/BedrockListener.java | 77 ------- .../geyser/api/network/RemoteServer.java | 70 ------ .../geysermc/geyser/api/util/TriState.java | 83 ------- build.gradle.kts | 18 +- core/build.gradle.kts | 2 +- .../protocol/java/JavaCommandsTranslator.java | 19 +- gradle/libs.versions.toml | 23 +- settings.gradle.kts | 4 - 46 files changed, 33 insertions(+), 3036 deletions(-) delete mode 100644 api/base/build.gradle.kts delete mode 100644 api/base/src/main/java/org/geysermc/api/Geyser.java delete mode 100644 api/base/src/main/java/org/geysermc/api/GeyserApiBase.java delete mode 100644 api/base/src/main/java/org/geysermc/api/connection/Connection.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/InputMode.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/UiProfile.java delete mode 100644 api/geyser/build.gradle.kts delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java delete mode 100644 api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts deleted file mode 100644 index 6b6fb8f46..000000000 --- a/api/base/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -dependencies { - api(libs.cumulus) - api(libs.events) { - exclude(group = "com.google.guava", module = "guava") - exclude(group = "org.lanternpowered", module = "lmbda") - } -} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java deleted file mode 100644 index 7543d1661..000000000 --- a/api/base/src/main/java/org/geysermc/api/Geyser.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * General API class for Geyser. - */ -@NonNull -public class Geyser { - private static GeyserApiBase api; - - /** - * Returns the base api. - * - * @return the base api - */ - @NonNull - public static GeyserApiBase api() { - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } - - return api; - } - - /** - * Returns the api of the given type. - * - * @param apiClass the api class - * @param the type - * @return the api of the given type - */ - @SuppressWarnings("unchecked") - public static T api(@NonNull Class apiClass) { - if (apiClass.isInstance(api)) { - return (T) api; - } - - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } else { - throw new RuntimeException("Api was not an instance of " + apiClass + "! Was " + api.getClass().getCanonicalName()); - } - } - - /** - * Registers the given api type. The api cannot be - * registered if {@link #isRegistered()} is true as - * an api has already been specified. - * - * @param api the api - */ - public static void set(@NonNull GeyserApiBase api) { - if (Geyser.api != null) { - throw new RuntimeException("Cannot redefine already registered api!"); - } - - Geyser.api = api; - } - - /** - * Gets if the api has been registered and - * is ready for usage. - * - * @return if the api has been registered - */ - public static boolean isRegistered() { - return api != null; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java deleted file mode 100644 index a845e37fd..000000000 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.connection.Connection; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.List; -import java.util.UUID; - -/** - * The base API class. - */ -public interface GeyserApiBase { - /** - * Gets the connection from the given UUID, if applicable. The player must be logged in to the Java server - * for this to return a non-null value. - * - * @param uuid the UUID of the connection - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByUuid(@NonNull UUID uuid); - - /** - * Gets the connection from the given XUID, if applicable. This method only works for online connections. - * - * @param xuid the XUID of the session - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByXuid(@NonNull String xuid); - - /** - * Method to determine if the given online player is a Bedrock player. - * - * @param uuid the uuid of the online player - * @return true if the given online player is a Bedrock player - */ - boolean isBedrockPlayer(@NonNull UUID uuid); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull Form form); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder); - - /** - * Transfer the given connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param uuid the uuid of the connection - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull UUID uuid, @NonNull String address, @IntRange(from = 0, to = 65535) int port); - - - /** - * Returns all the online connections. - */ - @NonNull - List onlineConnections(); - - /** - * Returns the amount of online connections. - */ - int onlineConnectionsCount(); - - /** - * Returns the prefix used by Floodgate. Will be null when the auth-type isn't Floodgate. - */ - @MonotonicNonNull - String usernamePrefix(); - - /** - * Returns the major API version. Bumped whenever a significant breaking change or feature addition is added. - */ - default int majorApiVersion() { - return 1; - } - - /** - * Returns the minor API version. May be bumped for new API additions. - */ - default int minorApiVersion() { - return 0; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java deleted file mode 100644 index 1cd7a9d13..000000000 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.connection; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.util.BedrockPlatform; -import org.geysermc.api.util.InputMode; -import org.geysermc.api.util.UiProfile; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.UUID; - -/** - * Represents a player connection. - */ -public interface Connection { - /** - * Returns the bedrock name of the connection. - */ - @NonNull String bedrockUsername(); - - /** - * Returns the java name of the connection. - */ - @MonotonicNonNull - String javaUsername(); - - /** - * Returns the UUID of the connection. - */ - @MonotonicNonNull - UUID javaUuid(); - - /** - * Returns the XUID of the connection. - */ - @NonNull String xuid(); - - /** - * Returns the version of the Bedrock client. - */ - @NonNull String version(); - - /** - * Returns the platform that the connection is playing on. - */ - @NonNull BedrockPlatform platform(); - - /** - * Returns the language code of the connection. - */ - @NonNull String languageCode(); - - /** - * Returns the User Interface Profile of the connection. - */ - @NonNull UiProfile uiProfile(); - - /** - * Returns the Input Mode of the Bedrock client. - */ - @NonNull InputMode inputMode(); - - /** - * Returns whether the connection is linked. - * This will always return false when the auth-type isn't Floodgate. - */ - boolean isLinked(); - - /** - * Sends a form to the connection and opens it. - * - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull Form form); - - /** - * Sends a form to the connection and opens it. - * - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull FormBuilder formBuilder); - - /** - * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); -} diff --git a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java deleted file mode 100644 index 15d0da027..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum BedrockPlatform { - UNKNOWN("Unknown"), - GOOGLE("Android"), - IOS("iOS"), - OSX("macOS"), - AMAZON("Amazon"), - GEARVR("Gear VR"), - HOLOLENS("Hololens"), - UWP("Windows"), - WIN32("Windows x86"), - DEDICATED("Dedicated"), - TVOS("Apple TV"), - PS4("PS4"), - NX("Switch"), - XBOX("Xbox One"), - WINDOWS_PHONE("Windows Phone"); - - private static final BedrockPlatform[] VALUES = values(); - - private final String displayName; - - BedrockPlatform(String displayName) { - this.displayName = displayName; - } - - /** - * Get the BedrockPlatform from the identifier. - * - * @param id the BedrockPlatform identifier - * @return The BedrockPlatform or {@link #UNKNOWN} if the platform wasn't found - */ - @NonNull - public static BedrockPlatform fromId(int id) { - return id < VALUES.length ? VALUES[id] : VALUES[0]; - } - - /** - * @return friendly display name of platform. - */ - @Override - public String toString() { - return displayName; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/util/InputMode.java b/api/base/src/main/java/org/geysermc/api/util/InputMode.java deleted file mode 100644 index 70346ffa5..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/InputMode.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum InputMode { - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, - CONTROLLER, - VR; - - private static final InputMode[] VALUES = values(); - - /** - * Get the InputMode from the identifier. - * - * @param id the InputMode identifier - * @return The InputMode or {@link #UNKNOWN} if the mode wasn't found - */ - @NonNull - public static InputMode fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java deleted file mode 100644 index cddb97260..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum UiProfile { - CLASSIC, POCKET; - - private static final UiProfile[] VALUES = values(); - - /** - * Get the UiProfile from the identifier. - * - * @param id the UiProfile identifier - * @return The UiProfile or {@link #CLASSIC} if the profile wasn't found - */ - @NonNull - public static UiProfile fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts deleted file mode 100644 index dcde85337..000000000 --- a/api/geyser/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id("geyser.api-conventions") -} - -dependencies { - api(projects.api) -} - -publishing { - publications.named("mavenJava") { - groupId = rootProject.group as String + ".geyser" - artifactId = "api" - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java deleted file mode 100644 index f86206d36..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.api.Geyser; -import org.geysermc.api.GeyserApiBase; -import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.extension.ExtensionManager; -import org.geysermc.geyser.api.network.BedrockListener; -import org.geysermc.geyser.api.network.RemoteServer; - -import java.util.List; -import java.util.UUID; - -/** - * Represents the API used in Geyser. - */ -public interface GeyserApi extends GeyserApiBase { - /** - * {@inheritDoc} - */ - @Override - @Nullable GeyserConnection connectionByUuid(@NonNull UUID uuid); - - /** - * {@inheritDoc} - */ - @Override - @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); - - /** - * {@inheritDoc} - */ - @NonNull - List onlineConnections(); - - /** - * Gets the {@link ExtensionManager}. - * - * @return the extension manager - */ - @NonNull - ExtensionManager extensionManager(); - - /** - * Provides an implementation for the specified API type. - * - * @param apiClass the builder class - * @param the implementation type - * @param the API type - * @return the builder instance - */ - @NonNull - R provider(@NonNull Class apiClass, @Nullable Object... args); - - /** - * Gets the {@link EventBus} for handling - * Geyser events. - * - * @return the event bus - */ - @NonNull - EventBus eventBus(); - - /** - * Gets the default {@link RemoteServer} configured - * within the config file that is used by default. - * - * @return the default remote server used within Geyser - */ - @NonNull - RemoteServer defaultRemoteServer(); - - /** - * Gets the {@link BedrockListener} used for listening - * for Minecraft: Bedrock Edition client connections. - * - * @return the listener used for Bedrock client connectins - */ - @NonNull - BedrockListener bedrockListener(); - - /** - * Gets the current {@link GeyserApiBase} instance. - * - * @return the current geyser api instance - */ - @NonNull - static GeyserApi api() { - return Geyser.api(GeyserApi.class); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java deleted file mode 100644 index 2f1f2b24d..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.command; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.GeyserApi; -import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.extension.Extension; - -import java.util.Collections; -import java.util.List; - -/** - * Represents a command. - */ -public interface Command { - - /** - * Gets the command name. - * - * @return the command name - */ - @NonNull - String name(); - - /** - * Gets the command description. - * - * @return the command description - */ - @NonNull - String description(); - - /** - * Gets the permission node associated with - * this command. - * - * @return the permission node for this command - */ - @NonNull - String permission(); - - /** - * Gets the aliases for this command. - * - * @return the aliases for this command - */ - @NonNull - List aliases(); - - /** - * Gets if this command is designed to be used only by server operators. - * - * @return if this command is designated to be used only by server operators. - */ - boolean isSuggestedOpOnly(); - - /** - * Gets if this command is executable on console. - * - * @return if this command is executable on console - */ - boolean isExecutableOnConsole(); - - /** - * Gets the subcommands associated with this - * command. Mainly used within the Geyser Standalone - * GUI to know what subcommands are supported. - * - * @return the subcommands associated with this command - */ - @NonNull - default List subCommands() { - return Collections.emptyList(); - } - - /** - * Used to send a deny message to Java players if this command can only be used by Bedrock players. - * - * @return true if this command can only be used by Bedrock players. - */ - default boolean isBedrockOnly() { - return false; - } - - /** - * Creates a new {@link Command.Builder} used to construct commands. - * - * @param extension the extension - * @param the source type - * @return a new command builder used to construct commands - */ - static Command.Builder builder(@NonNull Extension extension) { - return GeyserApi.api().provider(Builder.class, extension); - } - - interface Builder { - - /** - * Defines the source type to use for this command. - *

- * Command source types can be anything that extend - * {@link CommandSource}, such as {@link GeyserConnection}. - * This will guarantee that the source used in the executor - * is an instance of this source. - * - * @param sourceType the source type - * @return the builder - */ - Builder source(@NonNull Class sourceType); - - /** - * Sets the command name. - * - * @param name the command name - * @return the builder - */ - Builder name(@NonNull String name); - - /** - * Sets the command description. - * - * @param description the command description - * @return the builder - */ - Builder description(@NonNull String description); - - /** - * Sets the permission node. - * - * @param permission the permission node - * @return the builder - */ - Builder permission(@NonNull String permission); - - /** - * Sets the aliases. - * - * @param aliases the aliases - * @return the builder - */ - Builder aliases(@NonNull List aliases); - - /** - * Sets if this command is designed to be used only by server operators. - * - * @param suggestedOpOnly if this command is designed to be used only by server operators - * @return the builder - */ - Builder suggestedOpOnly(boolean suggestedOpOnly); - - /** - * Sets if this command is executable on console. - * - * @param executableOnConsole if this command is executable on console - * @return the builder - */ - Builder executableOnConsole(boolean executableOnConsole); - - /** - * Sets the subcommands. - * - * @param subCommands the subcommands - * @return the builder - */ - Builder subCommands(@NonNull List subCommands); - - /** - * Sets if this command is bedrock only. - * - * @param bedrockOnly if this command is bedrock only - * @return the builder - */ - Builder bedrockOnly(boolean bedrockOnly); - - /** - * Sets the {@link CommandExecutor} for this command. - * - * @param executor the command executor - * @return the builder - */ - Builder executor(@NonNull CommandExecutor executor); - - /** - * Builds the command. - * - * @return the command - */ - @NonNull - Command build(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java deleted file mode 100644 index 12a54ee90..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.command; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * Handles executing a command. - * - * @param the command source - */ -public interface CommandExecutor { - /** - * Executes the given {@link Command} with the given - * {@link CommandSource}. - * - * @param source the command source - * @param command the command - * @param args the arguments - */ - void execute(@NonNull T source, @NonNull Command command, @NonNull String[] args); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java deleted file mode 100644 index 45276e2c4..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.command; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * Represents an instance capable of sending commands. - */ -public interface CommandSource { - - /** - * The name of the command source. - * - * @return the name of the command source - */ - String name(); - - /** - * Sends the given message to the command source - * - * @param message the message to send - */ - void sendMessage(@NonNull String message); - - /** - * Sends the given messages to the command source - * - * @param messages the messages to send - */ - default void sendMessage(String[] messages) { - for (String message : messages) { - sendMessage(message); - } - } - - /** - * If this source is the console. - * - * @return true if this source is the console - */ - boolean isConsole(); - - /** - * Returns the locale of the command source. - * - * @return the locale of the command source. - */ - String locale(); - - /** - * Checks if this command source has the given permission - * - * @param permission The permission node to check - * @return true if this command source has a permission - */ - boolean hasPermission(String permission); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java deleted file mode 100644 index 13fd60407..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.connection; - -import org.geysermc.api.connection.Connection; -import org.geysermc.geyser.api.command.CommandSource; - -/** - * Represents a player connection used in Geyser. - */ -public interface GeyserConnection extends Connection, CommandSource { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java deleted file mode 100644 index 801bfa45f..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.event.bus.OwnedEventBus; -import org.geysermc.geyser.api.extension.Extension; - -import java.util.Set; - -/** - * Represents a bus capable of subscribing - * or "listening" to events and firing them. - */ -public interface EventBus extends OwnedEventBus> { - @Override - @NonNull - Set> subscribers(@NonNull Class eventClass); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java deleted file mode 100644 index 064dd55f6..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.GeyserApi; - -/** - * Represents an owner for an event that allows it - * to be registered through an {@link EventBus}. - */ -public interface EventRegistrar { - - /** - * Creates an {@link EventRegistrar} instance. - * - * @param object the object to wrap around - * @return an event registrar instance - */ - @NonNull - static EventRegistrar of(@NonNull Object object) { - return GeyserApi.api().provider(EventRegistrar.class, object); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java deleted file mode 100644 index 7f91d09a3..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.geysermc.event.Event; -import org.geysermc.event.subscribe.OwnedSubscriber; -import org.geysermc.geyser.api.extension.Extension; - -/** - * Represents a subscribed listener to a {@link Event}. Wraps around - * the event and is capable of unsubscribing from the event or give - * information about it. - * - * @param the class of the event - */ -public interface EventSubscriber extends OwnedSubscriber { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java deleted file mode 100644 index a58d35891..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.extension.Extension; - -import java.util.Set; - -/** - * An {@link EventBus} with additional methods that implicitly - * set the extension instance. - */ -public interface ExtensionEventBus extends org.geysermc.event.bus.EventBus> { - @Override - @NonNull Set> subscribers(@NonNull Class eventClass); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java deleted file mode 100644 index 9c5fffa2f..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event; - -import org.geysermc.event.Event; -import org.geysermc.event.subscribe.Subscriber; - -public interface ExtensionEventSubscriber extends Subscriber { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java deleted file mode 100644 index 158f14d53..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.connection; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.connection.GeyserConnection; - -/** - * An event that contains a {@link GeyserConnection}. - */ -public abstract class ConnectionEvent implements Event { - private final GeyserConnection connection; - - public ConnectionEvent(@NonNull GeyserConnection connection) { - this.connection = connection; - } - - /** - * Gets the {@link GeyserConnection}. - * - * @return the connection - */ - @NonNull - public GeyserConnection connection() { - return this.connection; - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java deleted file mode 100644 index e46492b36..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.downstream; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Cancellable; -import org.geysermc.geyser.api.connection.GeyserConnection; -import org.geysermc.geyser.api.event.connection.ConnectionEvent; - -import java.util.Set; - -/** - * Called when the Java server defines the commands available on the server. - *
- * This event is mapped to the existence of Brigadier on the server. - */ -public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable { - private final Set commands; - private boolean cancelled; - - public ServerDefineCommandsEvent(@NonNull GeyserConnection connection, @NonNull Set commands) { - super(connection); - this.commands = commands; - } - - /** - * A collection of commands sent from the server. Any element in this collection can be removed, but no element can - * be added. - * - * @return a collection of the commands sent over - */ - @NonNull - public Set commands() { - return this.commands; - } - - @Override - public boolean isCancelled() { - return this.cancelled; - } - - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } - - public interface CommandInfo { - /** - * Gets the name of the command. - * - * @return the name of the command - */ - String name(); - - /** - * Gets the description of the command. - * - * @return the description of the command - */ - String description(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java deleted file mode 100644 index 77d5efa65..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.command.Command; - -import java.util.Map; - -/** - * Called when commands are defined within Geyser. - * - * This event allows you to register new commands using the {@link #register(Command)} - * method and retrieve the default commands defined. - */ -public interface GeyserDefineCommandsEvent extends Event { - - /** - * Registers the given {@link Command} into the Geyser - * command manager. - * - * @param command the command to register - */ - void register(@NonNull Command command); - - /** - * Gets all the registered built-in {@link Command}s. - * - * @return all the registered built-in commands - */ - @NonNull - Map commands(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java deleted file mode 100644 index 0957b8551..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.item.custom.CustomItemData; -import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Called on Geyser's startup when looking for custom items. Custom items must be registered through this event. - * - * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. - */ -public interface GeyserDefineCustomItemsEvent extends Event { - /** - * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. - * - * @return a multimap of all the already registered custom items - */ - @NonNull - Map> getExistingCustomItems(); - - /** - * Gets the list of the already registered non-vanilla custom items. - * - * @return the list of the already registered non-vanilla custom items - */ - @NonNull - List getExistingNonVanillaCustomItems(); - - /** - * Registers a custom item with a base Java item. This is used to register items with custom textures and properties - * based on NBT data. - * - * @param identifier the base (java) item - * @param customItemData the custom item data to register - * @return if the item was registered - */ - boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); - - /** - * Registers a custom item with no base item. This is used for mods. - * - * @param customItemData the custom item data to register - * @return if the item was registered - */ - boolean register(@NonNull NonVanillaCustomItemData customItemData); -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java deleted file mode 100644 index e9b283ecb..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; - -import java.nio.file.Path; -import java.util.List; - -/** - * Called when resource packs are loaded within Geyser. - * - * @param resourcePacks a mutable list of the currently listed resource packs - */ -public record GeyserLoadResourcePacksEvent(@NonNull List resourcePacks) implements Event { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java deleted file mode 100644 index 8d145f615..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.extension.ExtensionManager; - -/** - * Called when Geyser has completed initializing. - * - * @param extensionManager the extension manager - * @param eventBus the event bus - */ -public record GeyserPostInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java deleted file mode 100644 index 8be89dafd..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.extension.ExtensionManager; - -/** - * Called when Geyser is starting to initialize. - * - * @param extensionManager the extension manager - * @param eventBus the event bus - */ -public record GeyserPreInitializeEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java deleted file mode 100644 index 7793ef997..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.event.lifecycle; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; -import org.geysermc.geyser.api.event.EventBus; -import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.extension.ExtensionManager; - -/** - * Called when Geyser is shutting down. - */ -public record GeyserShutdownEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java deleted file mode 100644 index 33fc159de..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.api.GeyserApiBase; -import org.geysermc.geyser.api.GeyserApi; -import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.event.ExtensionEventBus; - -import java.nio.file.Path; -import java.util.Objects; - -/** - * Represents an extension within Geyser. - */ -public interface Extension extends EventRegistrar { - - /** - * Gets if the extension is enabled - * - * @return true if the extension is enabled - */ - default boolean isEnabled() { - return this.extensionLoader().isEnabled(this); - } - - /** - * Enables or disables the extension - * - * @param enabled if the extension should be enabled - */ - default void setEnabled(boolean enabled) { - this.extensionLoader().setEnabled(this, enabled); - } - - /** - * Gets the extension's data folder - * - * @return the extension's data folder - */ - @NonNull - default Path dataFolder() { - return this.extensionLoader().dataFolder(this); - } - - /** - * Gets the {@link ExtensionEventBus}. - * - * @return the extension event bus - */ - @NonNull - default ExtensionEventBus eventBus() { - return this.extensionLoader().eventBus(this); - } - - /** - * Gets the {@link ExtensionManager}. - * - * @return the extension manager - */ - @NonNull - default ExtensionManager extensionManager() { - return this.geyserApi().extensionManager(); - } - - /** - * Gets the extension's name - * - * @return the extension's name - */ - @NonNull - default String name() { - return this.description().name(); - } - - /** - * Gets this extension's {@link ExtensionDescription}. - * - * @return the extension's description - */ - @NonNull - default ExtensionDescription description() { - return this.extensionLoader().description(this); - } - - /** - * Gets the extension's logger - * - * @return the extension's logger - */ - @NonNull - default ExtensionLogger logger() { - return this.extensionLoader().logger(this); - } - - /** - * Gets the {@link ExtensionLoader}. - * - * @return the extension loader - */ - @NonNull - default ExtensionLoader extensionLoader() { - return Objects.requireNonNull(this.extensionManager().extensionLoader()); - } - - /** - * Gets the {@link GeyserApiBase} instance - * - * @return the geyser api instance - */ - @NonNull - default GeyserApi geyserApi() { - return GeyserApi.api(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java deleted file mode 100644 index 2df3ee815..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.List; - -/** - * Represents the description of an {@link Extension}. - */ -public interface ExtensionDescription { - - /** - * Gets the extension's id. - * - * @return the extension's id - */ - @NonNull - String id(); - - /** - * Gets the extension's name. - * - * @return the extension's name - */ - @NonNull - String name(); - - /** - * Gets the extension's main class. - * - * @return the extension's main class - */ - @NonNull - String main(); - - /** - * Gets the extension's major api version - * - * @return the extension's major api version - */ - int majorApiVersion(); - - /** - * Gets the extension's minor api version - * - * @return the extension's minor api version - */ - int minorApiVersion(); - - /** - * Gets the extension's patch api version - * - * @return the extension's patch api version - */ - int patchApiVersion(); - - /** - * Gets the extension's api version. - * - * @return the extension's api version - */ - default String apiVersion() { - return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); - } - - /** - * Gets the extension's description. - * - * @return the extension's description - */ - @NonNull - String version(); - - /** - * Gets the extension's authors. - * - * @return the extension's authors - */ - @NonNull - List authors(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java deleted file mode 100644 index 30414d500..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.ExtensionEventBus; - -import java.nio.file.Path; - -/** - * The extension loader is responsible for loading, unloading, enabling and disabling extensions - */ -public abstract class ExtensionLoader { - /** - * Gets if the given {@link Extension} is enabled. - * - * @param extension the extension - * @return if the extension is enabled - */ - protected abstract boolean isEnabled(@NonNull Extension extension); - - /** - * Sets if the given {@link Extension} is enabled. - * - * @param extension the extension to enable - * @param enabled if the extension should be enabled - */ - protected abstract void setEnabled(@NonNull Extension extension, boolean enabled); - - /** - * Gets the given {@link Extension}'s data folder. - * - * @param extension the extension - * @return the data folder of the given extension - */ - @NonNull - protected abstract Path dataFolder(@NonNull Extension extension); - - /** - * Gets the given {@link Extension}'s {@link ExtensionDescription}. - * - * @param extension the extension - * @return the description of the given extension - */ - @NonNull - protected abstract ExtensionDescription description(@NonNull Extension extension); - - /** - * Gets the given {@link Extension}'s {@link ExtensionEventBus}. - * - * @param extension the extension - * @return the extension's event bus - */ - @NonNull - protected abstract ExtensionEventBus eventBus(@NonNull Extension extension); - - /** - * Gets the {@link ExtensionLogger} for the given {@link Extension}. - * - * @param extension the extension - * @return the extension logger for the given extension - */ - @NonNull - protected abstract ExtensionLogger logger(@NonNull Extension extension); - - /** - * Loads all extensions. - * - * @param extensionManager the extension manager - */ - protected abstract void loadAllExtensions(@NonNull ExtensionManager extensionManager); - - /** - * Registers the given {@link Extension} with the given {@link ExtensionManager}. - * - * @param extension the extension - * @param extensionManager the extension manager - */ - protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { - extensionManager.register(extension); - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java deleted file mode 100644 index 17e108455..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -/** - * This is the Geyser extension logger - */ -public interface ExtensionLogger { - /** - * Get the logger prefix - * - * @return the logger prefix - */ - String prefix(); - - /** - * Logs a severe message to console - * - * @param message the message to log - */ - void severe(String message); - - /** - * Logs a severe message and an exception to console - * - * @param message the message to log - * @param error the error to throw - */ - void severe(String message, Throwable error); - - /** - * Logs an error message to console - * - * @param message the message to log - */ - void error(String message); - - /** - * Logs an error message and an exception to console - * - * @param message the message to log - * @param error the error to throw - */ - void error(String message, Throwable error); - - /** - * Logs a warning message to console - * - * @param message the message to log - */ - void warning(String message); - - /** - * Logs an info message to console - * - * @param message the message to log - */ - void info(String message); - - /** - * Logs a debug message to console - * - * @param message the message to log - */ - void debug(String message); - - /** - * If debug is enabled for this logger - */ - boolean isDebug(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java deleted file mode 100644 index a9d0d7376..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.Collection; - -/** - * Manages Geyser {@link Extension}s - */ -public abstract class ExtensionManager { - - /** - * Gets an extension with the given name. - * - * @param name the name of the extension - * @return an extension with the given name - */ - @Nullable - public abstract Extension extension(@NonNull String name); - - /** - * Enables the given {@link Extension}. - * - * @param extension the extension to enable - */ - public abstract void enable(@NonNull Extension extension); - - /** - * Disables the given {@link Extension}. - * - * @param extension the extension to disable - */ - public abstract void disable(@NonNull Extension extension); - - /** - * Gets all the {@link Extension}s currently loaded. - * - * @return all the extensions currently loaded - */ - @NonNull - public abstract Collection extensions(); - - /** - * Gets the {@link ExtensionLoader}. - * - * @return the extension loader - */ - @Nullable - public abstract ExtensionLoader extensionLoader(); - - /** - * Registers an {@link Extension} with the given {@link ExtensionLoader}. - * - * @param extension the extension - */ - public abstract void register(@NonNull Extension extension); - - /** - * Loads all extensions from the given {@link ExtensionLoader}. - */ - protected final void loadAllExtensions(@NonNull ExtensionLoader extensionLoader) { - extensionLoader.loadAllExtensions(this); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java deleted file mode 100644 index 1fe88e9e9..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension.exception; - -/** - * Thrown when an extension's description is invalid. - */ -public class InvalidDescriptionException extends Exception { - public InvalidDescriptionException(Throwable cause) { - super(cause); - } - - public InvalidDescriptionException(String message) { - super(message); - } - - public InvalidDescriptionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java deleted file mode 100644 index 7fb6b6922..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension.exception; - -/** - * Thrown when an extension is invalid. - */ -public class InvalidExtensionException extends Exception { - public InvalidExtensionException(Throwable cause) { - super(cause); - } - - public InvalidExtensionException(String message) { - super(message); - } - - public InvalidExtensionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java deleted file mode 100644 index 17763fb77..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.GeyserApi; - -/** - * This is used to store data for a custom item. - */ -public interface CustomItemData { - /** - * Gets the item's name. - * - * @return the item's name - */ - @NonNull String name(); - - /** - * Gets the custom item options of the item. - * - * @return the custom item options of the item. - */ - CustomItemOptions customItemOptions(); - - /** - * Gets the item's display name. By default, this is the item's name. - * - * @return the item's display name - */ - @NonNull String displayName(); - - /** - * Gets the item's icon. By default, this is the item's name. - * - * @return the item's icon - */ - @NonNull String icon(); - - /** - * Gets if the item is allowed to be put into the offhand. - * - * @return true if the item is allowed to be used in the offhand, false otherwise - */ - boolean allowOffhand(); - - /** - * Gets the item's texture size. This is to resize the item if the texture is not 16x16. - * - * @return the item's texture size - */ - int textureSize(); - - /** - * Gets the item's render offsets. If it is null, the item will be rendered normally, with no offsets. - * - * @return the item's render offsets - */ - @Nullable CustomRenderOffsets renderOffsets(); - - static CustomItemData.Builder builder() { - return GeyserApi.api().provider(CustomItemData.Builder.class); - } - - interface Builder { - /** - * Will also set the display name and icon to the provided parameter, if it is currently not set. - */ - Builder name(@NonNull String name); - - Builder customItemOptions(@NonNull CustomItemOptions customItemOptions); - - Builder displayName(@NonNull String displayName); - - Builder icon(@NonNull String icon); - - Builder allowOffhand(boolean allowOffhand); - - Builder textureSize(int textureSize); - - Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); - - CustomItemData build(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java deleted file mode 100644 index ec26a6e37..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.GeyserApi; -import org.geysermc.geyser.api.util.TriState; - -import java.util.OptionalInt; - -/** - * This class represents the different ways you can register custom items - */ -public interface CustomItemOptions { - /** - * Gets if the item should be unbreakable. - * - * @return if the item should be unbreakable - */ - @NonNull TriState unbreakable(); - - /** - * Gets the item's custom model data predicate. - * - * @return the item's custom model data - */ - @NonNull OptionalInt customModelData(); - - /** - * Gets the item's damage predicate. - * - * @return the item's damage predicate - */ - @NonNull OptionalInt damagePredicate(); - - /** - * Checks if the item has at least one option set - * - * @return true if the item at least one options set - */ - default boolean hasCustomItemOptions() { - return this.unbreakable() != TriState.NOT_SET || - this.customModelData().isPresent() || - this.damagePredicate().isPresent(); - } - - static CustomItemOptions.Builder builder() { - return GeyserApi.api().provider(CustomItemOptions.Builder.class); - } - - interface Builder { - Builder unbreakable(boolean unbreakable); - - Builder customModelData(int customModelData); - - Builder damagePredicate(int damagePredicate); - - CustomItemOptions build(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java deleted file mode 100644 index f81da0ae2..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom; - -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * This class is used to store the render offsets of custom items. - */ -public record CustomRenderOffsets(@Nullable Hand mainHand, @Nullable Hand offhand) { - /** - * The hand that is used for the offset. - */ - public record Hand(@Nullable Offset firstPerson, @Nullable Offset thirdPerson) { - } - - /** - * The offset of the item. - */ - public record Offset(@Nullable OffsetXYZ position, @Nullable OffsetXYZ rotation, @Nullable OffsetXYZ scale) { - } - - /** - * X, Y and Z positions for the offset. - */ - public record OffsetXYZ(float x, float y, float z) { - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java deleted file mode 100644 index d2cef637a..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom; - -import org.checkerframework.checker.index.qual.NonNegative; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.GeyserApi; - -import java.util.OptionalInt; -import java.util.Set; - -/** - * Represents a completely custom item that is not based on an existing vanilla Minecraft item. - */ -public interface NonVanillaCustomItemData extends CustomItemData { - /** - * Gets the java identifier for this item. - * - * @return The java identifier for this item. - */ - @NonNull String identifier(); - - /** - * Gets the java item id of the item. - * - * @return the java item id of the item - */ - @NonNegative int javaId(); - - /** - * Gets the stack size of the item. - * - * @return the stack size of the item - */ - @NonNegative int stackSize(); - - /** - * Gets the max damage of the item. - * - * @return the max damage of the item - */ - int maxDamage(); - - /** - * Gets the tool type of the item. - * - * @return the tool type of the item - */ - @Nullable String toolType(); - - /** - * Gets the tool tier of the item. - * - * @return the tool tier of the item - */ - @Nullable String toolTier(); - - /** - * Gets the armor type of the item. - * - * @return the armor type of the item - */ - @Nullable String armorType(); - - /** - * Gets the armor protection value of the item. - * - * @return the armor protection value of the item - */ - int protectionValue(); - - /** - * Gets the item's translation string. - * - * @return the item's translation string - */ - @Nullable String translationString(); - - /** - * Gets the repair materials of the item. - * - * @return the repair materials of the item - */ - @Nullable Set repairMaterials(); - - /** - * Gets the item's creative category, or tab id. - * - * @return the item's creative category - */ - @NonNull OptionalInt creativeCategory(); - - /** - * Gets the item's creative group. - * - * @return the item's creative group - */ - @Nullable String creativeGroup(); - - /** - * Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and - * normally allow the player to equip it. This is not meant for armor. - * - * @return if the item is a hat - */ - boolean isHat(); - - /** - * Gets if the item is a tool. This is used to set the render type of the item, if the item is handheld. - * - * @return if the item is a tool - */ - boolean isTool(); - - static NonVanillaCustomItemData.Builder builder() { - return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class); - } - - interface Builder extends CustomItemData.Builder { - Builder name(@NonNull String name); - - Builder identifier(@NonNull String identifier); - - Builder javaId(@NonNegative int javaId); - - Builder stackSize(@NonNegative int stackSize); - - Builder maxDamage(int maxDamage); - - Builder toolType(@Nullable String toolType); - - Builder toolTier(@Nullable String toolTier); - - Builder armorType(@Nullable String armorType); - - Builder protectionValue(int protectionValue); - - Builder translationString(@Nullable String translationString); - - Builder repairMaterials(@Nullable Set repairMaterials); - - Builder creativeCategory(int creativeCategory); - - Builder creativeGroup(@Nullable String creativeGroup); - - Builder hat(boolean isHat); - - Builder tool(boolean isTool); - - @Override - Builder displayName(@NonNull String displayName); - - @Override - Builder allowOffhand(boolean allowOffhand); - - @Override - Builder textureSize(int textureSize); - - @Override - Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); - - NonVanillaCustomItemData build(); - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java deleted file mode 100644 index 3176f3384..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.network; - -import java.util.Locale; - -/** - * The authentication types that a Java server can be on connection. - */ -public enum AuthType { - OFFLINE, - ONLINE, - /** - * The internal name for connecting to an online mode server without needing a Java account. The presence of this - * authentication type does not necessarily mean the Floodgate plugin is installed; it only means that this - * authentication type will be attempted. - */ - FLOODGATE; - - private static final AuthType[] VALUES = values(); - - /** - * Convert the AuthType string (from config) to the enum, ONLINE on fail - * - * @param name AuthType string - * - * @return The converted AuthType - */ - public static AuthType getByName(String name) { - String upperCase = name.toUpperCase(Locale.ROOT); - for (AuthType type : VALUES) { - if (type.name().equals(upperCase)) { - return type; - } - } - return ONLINE; - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java deleted file mode 100644 index 61fe286aa..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.network; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * The listener that handles connections from Minecraft: - * Bedrock Edition. - */ -public interface BedrockListener { - - /** - * Gets the address used for listening for Bedrock - * connections from. - * - * @return the listening address - */ - @NonNull - String address(); - - /** - * Gets the port used for listening for Bedrock - * connections from. - * - * @return the listening port - */ - int port(); - - /** - * Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled. - *

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

- * This is the second line that will be displayed. - * - * @return the secondary MOTD shown to Bedrock players. - */ - String secondaryMotd(); - - /** - * Gets the server name that is sent to Bedrock clients. - * - * @return the server sent to Bedrock clients - */ - String serverName(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java deleted file mode 100644 index 8ac5d8a03..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.network; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * Represents the Java server that Geyser is connecting to. - */ -public interface RemoteServer { - - /** - * Gets the IP address of the remote server. - * - * @return the IP address of the remote server - */ - String address(); - - /** - * Gets the port of the remote server. - * - * @return the port of the remote server - */ - int port(); - - /** - * Gets the protocol version of the remote server. - * - * @return the protocol version of the remote server - */ - int protocolVersion(); - - /** - * Gets the Minecraft version of the remote server. - * - * @return the Minecraft version of the remote server - */ - String minecraftVersion(); - - /** - * Gets the {@link AuthType} required by the remote server. - * - * @return the auth type required by the remote server - */ - @NonNull - AuthType authType(); -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java b/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java deleted file mode 100644 index 457a38e32..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * This is a way to represent a boolean, but with a non set value added. - * This class was inspired by adventure's version https://github.com/KyoriPowered/adventure/blob/main/4/api/src/main/java/net/kyori/adventure/util/TriState.java - */ -public enum TriState { - /** - * Describes a value that is not set, null, or not present. - */ - NOT_SET, - - /** - * Describes a true value. - */ - TRUE, - - /** - * Describes a false value. - */ - FALSE; - - /** - * Converts the TriState to a boolean. - * - * @return the boolean value of the TriState - */ - public @Nullable Boolean toBoolean() { - return switch (this) { - case TRUE -> true; - case FALSE -> false; - default -> null; - }; - } - - /** - * Creates a TriState from a boolean. - * - * @param value the Boolean value - * @return the created TriState - */ - public static @NonNull TriState fromBoolean(@Nullable Boolean value) { - return value == null ? NOT_SET : fromBoolean(value.booleanValue()); - } - - /** - * Creates a TriState from a primitive boolean. - * - * @param value the boolean value - * @return the created TriState - */ - public @NonNull static TriState fromBoolean(boolean value) { - return value ? TRUE : FALSE; - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 06c2e987b..4304811ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } allprojects { - group = "org.geysermc" + group = "org.geysermc.geyser" version = "2.1.0-SNAPSHOT" description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers." @@ -23,8 +23,6 @@ val platforms = setOf( projects.velocity ).map { it.dependencyProject } -val api: Project = projects.api.dependencyProject - subprojects { apply { plugin("java-library") @@ -32,16 +30,8 @@ subprojects { plugin("geyser.build-logic") } - val relativePath = projectDir.relativeTo(rootProject.projectDir).path - - if (relativePath.contains("api")) { - plugins.apply("geyser.api-conventions") - } else { - group = rootProject.group as String + ".geyser" - when (this) { - in platforms -> plugins.apply("geyser.platform-conventions") - api -> plugins.apply("geyser.publish-conventions") - else -> plugins.apply("geyser.base-conventions") - } + when (this) { + in platforms -> plugins.apply("geyser.platform-conventions") + else -> plugins.apply("geyser.base-conventions") } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 994325ea0..c9a929bcc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -7,8 +7,8 @@ plugins { } dependencies { - api(projects.geyserApi) api(projects.common) + api(libs.geyser.api) // Jackson JSON and YAML serialization api(libs.bundles.jackson) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index 11311b63c..8fd425df4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -46,7 +46,7 @@ import lombok.ToString; import net.kyori.adventure.text.format.NamedTextColor; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent; +import org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.registry.BlockRegistries; @@ -150,12 +150,20 @@ public class JavaCommandsTranslator extends PacketTranslator new HashSet<>()).add(node.getName().toLowerCase()); } - ServerDefineCommandsEvent event = new ServerDefineCommandsEvent(session, commands.keySet()); - session.getGeyser().eventBus().fire(event); + var eventBus = session.getGeyser().eventBus(); + + var event = new ServerDefineCommandsEvent(session, commands.keySet()); + eventBus.fire(event); if (event.isCancelled()) { return; } + var oldEvent = new org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent(session, commands.keySet()); + eventBus.fire(oldEvent); + if (oldEvent.isCancelled()) { + return; + } + // The command flags, not sure what these do apart from break things List flags = Collections.emptyList(); @@ -258,7 +266,10 @@ public class JavaCommandsTranslator extends PacketTranslator Date: Sat, 7 Jan 2023 14:46:22 -0500 Subject: [PATCH 22/38] Should fix some instances of chat not showing --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f7f488f17..a54513df9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ websocket = "1.5.1" protocol = "2.9.15-20221129.204554-2" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" -mcprotocollib = "1.19.3-20230104.210231-9" +mcprotocollib = "1.19.3-20230107.194116-10" packetlib = "3.0.1" adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" From 0b80c589584129ba2280463f4c65fcd84c6d77db Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 16 Jan 2023 19:16:01 -0500 Subject: [PATCH 23/38] Handle invalid items in shulker box --- .../inventory/item/nbt/ShulkerBoxItemTranslator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java index f95c54e18..29d97dc27 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java @@ -52,6 +52,11 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { ItemMapping boxMapping = session.getItemMappings().getMapping(Identifier.formalize(((StringTag) itemData.get("id")).getValue())); + if (boxMapping == null) { + // If invalid ID + continue; + } + boxItemTag.put(new StringTag("Name", boxMapping.getBedrockIdentifier())); boxItemTag.put(new ShortTag("Damage", (short) boxMapping.getBedrockData())); boxItemTag.put(new ByteTag("Count", MathUtils.getNbtByte(itemData.get("Count").getValue()))); From b8040a1d9849d9f4ba62ff9e5a9a2447820dd438 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Fri, 20 Jan 2023 07:02:20 -0800 Subject: [PATCH 24/38] Fix some instances of chunks not appearing (#3498) --- .../main/java/org/geysermc/geyser/session/GeyserSession.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 9ca124393..b629fa4d4 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1402,6 +1402,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } public void setServerRenderDistance(int renderDistance) { + // +1 is for Fabric and Spigot + // Without the client misses loading some chunks per https://github.com/GeyserMC/Geyser/issues/3490 + // Fog still appears essentially normally + renderDistance = renderDistance + 1; this.serverRenderDistance = renderDistance; ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); From 48d78720a170ba7630f26d415f1905400fe36d25 Mon Sep 17 00:00:00 2001 From: David Choo <4722249+davchoo@users.noreply.github.com> Date: Sun, 22 Jan 2023 13:23:16 -0500 Subject: [PATCH 25/38] Apply fallback skins to custom skulls with invalid or empty texture values (#3515) --- .../entity/type/player/SkullPlayerEntity.java | 14 +++- .../geyser/session/cache/SkullCache.java | 4 +- .../org/geysermc/geyser/skin/SkinManager.java | 40 ++++++---- .../geysermc/geyser/skin/SkinProvider.java | 78 ++++++++++--------- .../geyser/skin/SkullSkinManager.java | 52 ++++++++----- .../entity/SkullBlockEntityTranslator.java | 65 +++++++++++----- 6 files changed, 159 insertions(+), 94 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index c2af2e36b..369436b21 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -26,17 +26,20 @@ package org.geysermc.geyser.entity.type.player; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; +import lombok.Getter; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.SkullCache; import org.geysermc.geyser.skin.SkullSkinManager; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -46,6 +49,12 @@ import java.util.concurrent.TimeUnit; */ public class SkullPlayerEntity extends PlayerEntity { + @Getter + private UUID skullUUID; + + @Getter + private Vector3i skullPosition; + public SkullPlayerEntity(GeyserSession session, long geyserId) { super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null); } @@ -102,11 +111,14 @@ public class SkullPlayerEntity extends PlayerEntity { } public void updateSkull(SkullCache.Skull skull) { - if (!skull.getTexturesProperty().equals(getTexturesProperty())) { + skullPosition = skull.getPosition(); + + if (!Objects.equals(skull.getTexturesProperty(), getTexturesProperty()) || !Objects.equals(skullUUID, skull.getUuid())) { // Make skull invisible as we change skins setFlag(EntityFlag.INVISIBLE, true); updateBedrockMetadata(); + skullUUID = skull.getUuid(); setTexturesProperty(skull.getTexturesProperty()); SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java index f26e1cce3..6b25e84b4 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -71,8 +71,9 @@ public class SkullCache { this.skullRenderDistanceSquared = distance * distance; } - public void putSkull(Vector3i position, String texturesProperty, int blockState) { + public void putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) { Skull skull = skulls.computeIfAbsent(position, Skull::new); + skull.uuid = uuid; skull.texturesProperty = texturesProperty; skull.blockState = blockState; @@ -201,6 +202,7 @@ public class SkullCache { @RequiredArgsConstructor @Data public static class Skull { + private UUID uuid; private String texturesProperty; private int blockState; private SkullPlayerEntity entity; diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 800b71c96..48233fe3d 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; @@ -69,7 +70,7 @@ public class SkinManager { // The server either didn't have a texture to send, or we didn't have the texture ID cached. // Let's see if this player is a Bedrock player, and if so, let's pull their skin. // Otherwise, grab the default player skin - SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity); + SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity.getUuid()); if (skin == null) { skin = fallbackSkinData.skin(); geometry = fallbackSkinData.geometry(); @@ -255,24 +256,28 @@ public class SkinManager { * @return The built GameProfileData */ public static @Nullable GameProfileData from(PlayerEntity entity) { - try { - String texturesProperty = entity.getTexturesProperty(); + String texturesProperty = entity.getTexturesProperty(); + if (texturesProperty == null) { + // Likely offline mode + return null; + } - if (texturesProperty == null) { - // Likely offline mode - return null; - } + try { return loadFromJson(texturesProperty); - } catch (IOException exception) { - GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); + } catch (Exception exception) { + if (entity instanceof SkullPlayerEntity skullEntity) { + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for skull at " + skullEntity.getSkullPosition() + " with Value: " + texturesProperty); + } else { + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername() + " with Value: " + texturesProperty); + } if (GeyserImpl.getInstance().getConfig().isDebugMode()) { exception.printStackTrace(); } - return null; } + return null; } - private static GameProfileData loadFromJson(String encodedJson) throws IOException { + private static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException { JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); @@ -285,14 +290,23 @@ public class SkinManager { return null; } - String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); + String skinUrl; + JsonNode skinUrlNode = skinTexture.get("url"); + if (skinUrlNode != null && skinUrlNode.isTextual()) { + skinUrl = skinUrlNode.asText().replace("http://", "https://"); + } else { + return null; + } boolean isAlex = skinTexture.has("metadata"); String capeUrl = null; JsonNode capeTexture = textures.get("CAPE"); if (capeTexture != null) { - capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); + JsonNode capeUrlNode = capeTexture.get("url"); + if (capeUrlNode != null && capeUrlNode.isTextual()) { + capeUrl = capeUrlNode.asText().replace("http://", "https://"); + } } return new GameProfileData(skinUrl, capeUrl, isAlex); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 61f24ac1e..38ff92e8f 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -26,9 +26,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import it.unimi.dsi.fastutil.bytes.ByteArrays; @@ -172,14 +169,13 @@ public class SkinProvider { /** * If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback. */ - static SkinData determineFallbackSkinData(PlayerEntity entity) { + static SkinData determineFallbackSkinData(UUID uuid) { Skin skin = null; Cape cape = null; SkinGeometry geometry = SkinGeometry.WIDE; if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { // Let's see if this player is a Bedrock player, and if so, let's pull their skin. - UUID uuid = entity.getUuid(); GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { String skinId = session.getClientData().getSkinId(); @@ -192,7 +188,7 @@ public class SkinProvider { if (skin == null) { // We don't have a skin for the player right now. Fall back to a default. - ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(entity.getUuid()); + ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(uuid); skin = providedSkin.getData(); geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; } @@ -232,7 +228,7 @@ public class SkinProvider { SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); if (data == null) { // This player likely does not have a textures property - return CompletableFuture.completedFuture(determineFallbackSkinData(entity)); + return CompletableFuture.completedFuture(determineFallbackSkinData(entity.getUuid())); } return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) @@ -597,48 +593,23 @@ public class SkinProvider { } /** - * If a skull has a username but no textures, request them. + * Request textures from a player's UUID * - * @param skullOwner the CompoundTag of the skull with no textures + * @param uuid the player's UUID without any hyphens * @return a completable GameProfile with textures included */ - public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { + public static CompletableFuture requestTexturesFromUUID(String uuid) { return CompletableFuture.supplyAsync(() -> { - Tag uuidTag = skullOwner.get("Id"); - String uuidToString = ""; - JsonNode node; - boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check - - if (!retrieveUuidFromInternet) { - int[] uuidAsArray = ((IntArrayTag) uuidTag).getValue(); - // thank u viaversion - UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL), - (long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL)); - retrieveUuidFromInternet = uuid.version() != 4; - uuidToString = uuid.toString().replace("-", ""); - } - try { - if (retrieveUuidFromInternet) { - // Offline skin, or no present UUID - node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + skullOwner.get("Name").getValue()); - JsonNode id = node.get("id"); - if (id == null) { - GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + skullOwner.get("Name").getValue()); - return null; - } - uuidToString = id.asText(); - } - - // Get textures from UUID - node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString); + JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid); JsonNode properties = node.get("properties"); if (properties == null) { - GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); + GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid); return null; } return node.get("properties").get(0).get("value").asText(); } catch (Exception e) { + GeyserImpl.getInstance().getLogger().debug("Unable to request textures for " + uuid); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { e.printStackTrace(); } @@ -647,6 +618,37 @@ public class SkinProvider { }, EXECUTOR_SERVICE); } + /** + * Request textures from a player's username + * + * @param username the player's username + * @return a completable GameProfile with textures included + */ + public static CompletableFuture requestTexturesFromUsername(String username) { + return CompletableFuture.supplyAsync(() -> { + try { + // Offline skin, or no present UUID + JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); + JsonNode id = node.get("id"); + if (id == null) { + GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username); + return null; + } + return id.asText(); + } catch (Exception e) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + return null; + } + }, EXECUTOR_SERVICE).thenCompose(uuid -> { + if (uuid == null) { + return CompletableFuture.completedFuture(null); + } + return requestTexturesFromUUID(uuid); + }); + } + private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { if (provider == CapeProvider.FIVEZIG) return readFiveZigCape(imageUrl); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 2759b1408..7f1605561 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -29,11 +29,12 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import java.util.Collections; +import java.util.function.BiConsumer; import java.util.function.Consumer; public class SkullSkinManager extends SkinManager { @@ -48,28 +49,37 @@ public class SkullSkinManager extends SkinManager { ); } - public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, + public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session, Consumer skinConsumer) { + BiConsumer applySkin = (skin, throwable) -> { + try { + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); + } + + if (skinConsumer != null) { + skinConsumer.accept(skin); + } + }; + GameProfileData data = GameProfileData.from(entity); - - SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) - .whenCompleteAsync((skin, throwable) -> { - try { - PlayerSkinPacket packet = new PlayerSkinPacket(); - packet.setUuid(entity.getUuid()); - packet.setOldSkinName(""); - packet.setNewSkinName(skin.getTextureUrl()); - packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); - packet.setTrustedSkin(true); - session.sendUpstreamPacket(packet); - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); - } - - if (skinConsumer != null) { - skinConsumer.accept(skin); - } - }); + if (data == null) { + GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() + + " with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID()); + // No texture available, fallback using the UUID + SkinProvider.SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID()); + applySkin.accept(fallback.skin(), null); + } else { + SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) + .whenCompleteAsync(applySkin); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 94e2d4767..2130206e1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.math.vector.Vector3i; @@ -35,7 +36,10 @@ import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinProvider; +import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.UUID; import java.util.concurrent.CompletableFuture; @BlockEntity(type = BlockEntityType.SKULL) @@ -53,33 +57,54 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements builder.put("SkullType", skullVariant); } - private static CompletableFuture getTextures(CompoundTag tag) { - CompoundTag owner = tag.get("SkullOwner"); - if (owner != null) { - CompoundTag properties = owner.get("Properties"); - if (properties == null) { - return SkinProvider.requestTexturesFromUsername(owner); - } - - ListTag textures = properties.get("textures"); - LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); - StringTag texture = (StringTag) tag1.get("Value"); - return CompletableFuture.completedFuture(texture.getValue()); + private static UUID getUUID(CompoundTag owner) { + if (owner.get("Id") instanceof IntArrayTag uuidTag && uuidTag.length() == 4) { + int[] uuidAsArray = uuidTag.getValue(); + // thank u viaversion + return new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL), + (long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL)); } - return CompletableFuture.completedFuture(null); + // Convert username to an offline UUID + String username = null; + if (owner.get("Name") instanceof StringTag nameTag) { + username = nameTag.getValue().toLowerCase(Locale.ROOT); + } + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); + } + + private static CompletableFuture getTextures(CompoundTag owner, UUID uuid) { + CompoundTag properties = owner.get("Properties"); + if (properties == null) { + if (uuid != null && uuid.version() == 4) { + String uuidString = uuid.toString().replace("-", ""); + return SkinProvider.requestTexturesFromUUID(uuidString); + } else if (owner.get("Name") instanceof StringTag nameTag) { + // Fall back to username if UUID was missing or was an offline mode UUID + return SkinProvider.requestTexturesFromUsername(nameTag.getValue()); + } + return CompletableFuture.completedFuture(null); + } + + ListTag textures = properties.get("textures"); + LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); + StringTag texture = (StringTag) tag1.get("Value"); + return CompletableFuture.completedFuture(texture.getValue()); } public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { Vector3i blockPosition = Vector3i.from(posX, posY, posZ); - getTextures(tag).whenComplete((texturesProperty, throwable) -> { - if (texturesProperty == null) { - session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); - return; - } + CompoundTag owner = tag.get("SkullOwner"); + if (owner == null) { + session.getSkullCache().removeSkull(blockPosition); + return; + } + + UUID uuid = getUUID(owner); + getTextures(owner, uuid).whenComplete((texturesProperty, throwable) -> { if (session.getEventLoop().inEventLoop()) { - session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); + session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState); } else { - session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState)); + session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState)); } }); } From af5d03f5dd69f06255627f57127caecb54b48d35 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:05:04 -0500 Subject: [PATCH 26/38] Show teams in command suggestions --- .../geysermc/geyser/scoreboard/Scoreboard.java | 12 ++++++++++++ .../geysermc/geyser/session/GeyserSession.java | 17 +++++++++++++++++ .../protocol/java/JavaCommandsTranslator.java | 15 ++++++++++++++- gradle/libs.versions.toml | 2 +- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index a6e80a375..f26d5846d 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -37,6 +37,7 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.jetbrains.annotations.Contract; import javax.annotation.Nullable; import java.util.*; @@ -132,6 +133,10 @@ public final class Scoreboard { team = new Team(this, teamName); team.addEntities(players); teams.put(teamName, team); + + // Update command parameters - is safe to send even if the command enum doesn't exist on the client (as of 1.19.51) + session.addCommandEnum("Geyser_Teams", team.getId()); + return team; } @@ -343,9 +348,16 @@ public final class Scoreboard { // We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly // With the player's lack of a team in visibility checks updateEntityNames(remove, remove.getEntities(), true); + + session.removeCommandEnum("Geyser_Teams", remove.getId()); } } + @Contract("-> new") + public String[] getTeamNames() { + return teams.keySet().toArray(new String[0]); + } + /** * Updates the display names of all entities in a given team. * @param teamChange the players have either joined or left the team. Used for optimizations when just the display name updated. diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index b629fa4d4..33655beda 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -67,7 +67,9 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.command.SoftEnumUpdateType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; import io.netty.channel.Channel; @@ -1895,4 +1897,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { sendUpstreamPacket(transferPacket); return true; } + + public void addCommandEnum(String name, String... enums) { + softEnumPacket(name, SoftEnumUpdateType.ADD, enums); + } + + public void removeCommandEnum(String name, String... enums) { + softEnumPacket(name, SoftEnumUpdateType.REMOVE, enums); + } + + private void softEnumPacket(String name, SoftEnumUpdateType type, String... enums) { + UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket(); + packet.setType(type); + packet.setSoftEnum(new CommandEnumData(name, enums, true)); + sendUpstreamPacket(packet); + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index 11311b63c..bd0fab85f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -240,6 +240,7 @@ public class JavaCommandsTranslator extends PacketTranslator handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false); case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true); case DIMENSION -> context.session.getLevels(); + case TEAM -> context.getTeams(); // Note: as of Java 1.19.3, objectives are currently parsed from the server default -> CommandParam.STRING; }; } @@ -271,6 +272,7 @@ public class JavaCommandsTranslator extends PacketTranslator Date: Sun, 29 Jan 2023 22:15:26 -0500 Subject: [PATCH 27/38] Fix /geyser reload on Spigot Fixes #3478 --- .../geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 5f0061382..1be2eb32a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -195,6 +195,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserConfig.loadFloodgate(this); + this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); + this.geyserCommandManager.init(); + if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes Bukkit.getPluginManager().registerEvents(new Listener() { @@ -206,9 +209,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } }, this); - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); - this.geyserCommandManager.init(); - // Because Bukkit locks its command map upon startup, we need to // add our plugin commands in onEnable, but populating the executor // can happen at any time From c909b2b1a52ee617961b56cd8677f2f5fa1a1969 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 31 Jan 2023 13:01:20 -0500 Subject: [PATCH 28/38] Fix #3521 --- .../player/BedrockMovePlayerTranslator.java | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index 6078b7ebd..c6f42c48c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -92,11 +92,29 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= packet.getPosition().getY()) { + int floorY = position.getFloorY(); + // The void floor is offset about 40 blocks below the bottom of the world + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int voidFloorLocation = bedrockDimension.minY() - 40; + teleportThroughVoidFloor = floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation; + if (teleportThroughVoidFloor) { + // https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground. + onGround = false; + } + } else { + teleportThroughVoidFloor = false; + } + Packet movePacket; if (rotationChanged) { // Send rotation updates as well movePacket = new ServerboundMovePlayerPosRotPacket( - packet.isOnGround(), + onGround, position.getX(), position.getY(), position.getZ(), yaw, pitch ); @@ -105,35 +123,26 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= packet.getPosition().getY(); - entity.setPositionManual(packet.getPosition()); - entity.setOnGround(packet.isOnGround()); + entity.setOnGround(onGround); // Send final movement changes session.sendDownstreamPacket(movePacket); - if (notMovingUp) { - int floorY = position.getFloorY(); - // The void floor is offset about 40 blocks below the bottom of the world - BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); - int voidFloorLocation = bedrockDimension.minY() - 40; - if (floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation) { - // Work around there being a floor at the bottom of the world and teleport the player below it - // Moving from below to above the void floor works fine - entity.setPosition(entity.getPosition().sub(0, 4f, 0)); - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); - movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); - session.sendUpstreamPacket(movePlayerPacket); - } + if (teleportThroughVoidFloor) { + // Work around there being a floor at the bottom of the world and teleport the player below it + // Moving from below to above the void floor works fine + entity.setPosition(entity.getPosition().sub(0, 4f, 0)); + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(entity.getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); + session.sendUpstreamPacket(movePlayerPacket); } session.getSkullCache().updateVisibleSkulls(); From 0388785ea7d37cbdd5f4af7e02363c040d2026e9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:25:05 -0500 Subject: [PATCH 29/38] Fix some instances of team-applied nametags not working Fixes #3531 --- .../entity/type/player/PlayerEntity.java | 35 ++++++------------- .../geyser/scoreboard/Scoreboard.java | 21 ++++++----- .../org/geysermc/geyser/scoreboard/Team.java | 2 ++ 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 3e3a298bd..3501eb296 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -100,6 +100,7 @@ public class PlayerEntity extends LivingEntity { super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); this.username = username; + this.nametag = username; this.texturesProperty = texturesProperty; } @@ -119,7 +120,7 @@ public class PlayerEntity extends LivingEntity { } // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now - updateDisplayName(null, false); + updateDisplayName(session.getWorldCache().getScoreboard().getTeamFor(username)); AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); addPlayerPacket.setUuid(uuid); @@ -315,19 +316,10 @@ public class PlayerEntity extends LivingEntity { } //todo this will become common entity logic once UUID support is implemented for them - /** - * @param useGivenTeam even if there is no team, update the username in the entity metadata anyway, and don't look for a team - */ - public void updateDisplayName(@Nullable Team team, boolean useGivenTeam) { - if (team == null && !useGivenTeam) { - // Only search for the team if we are not supposed to use the given team - // If the given team is null, this is intentional that we are being removed from the team - team = session.getWorldCache().getScoreboard().getTeamFor(username); - } - + public void updateDisplayName(@Nullable Team team) { boolean needsUpdate; - String newDisplayName = this.username; if (team != null) { + String newDisplayName; if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { TeamColor color = team.getColor(); String chatColor = MessageTranslator.toChatColor(color); @@ -339,23 +331,16 @@ public class PlayerEntity extends LivingEntity { // The name is not visible to the session player; clear name newDisplayName = ""; } - needsUpdate = useGivenTeam && !newDisplayName.equals(nametag); - nametag = newDisplayName; - dirtyMetadata.put(EntityData.NAMETAG, newDisplayName); - } else if (useGivenTeam) { - // The name has reset, if it was previously something else - needsUpdate = !newDisplayName.equals(nametag); - dirtyMetadata.put(EntityData.NAMETAG, this.username); + needsUpdate = !newDisplayName.equals(this.nametag); + this.nametag = newDisplayName; } else { - needsUpdate = false; + // The name has reset, if it was previously something else + needsUpdate = !this.nametag.equals(this.username); + this.nametag = this.username; } if (needsUpdate) { - // Update the metadata as it won't be updated later - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.getMetadata().put(EntityData.NAMETAG, newDisplayName); - packet.setRuntimeEntityId(geyserId); - session.sendUpstreamPacket(packet); + dirtyMetadata.put(EntityData.NAMETAG, this.nametag); } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index f26d5846d..56e1e67f8 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; @@ -56,6 +57,13 @@ public final class Scoreboard { @Getter private final Map objectiveSlots = new EnumMap<>(ScoreboardPosition.class); private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads + /** + * Required to preserve vanilla behavior, which also uses a map. + * Otherwise, for example, if TAB has a team for a player and vanilla has a team, "race conditions" that do not + * match vanilla could occur. + */ + @Getter + private final Map playerToTeam = new Object2ObjectOpenHashMap<>(); private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -333,12 +341,7 @@ public final class Scoreboard { } public Team getTeamFor(String entity) { - for (Team team : teams.values()) { - if (team.hasEntity(entity)) { - return team; - } - } - return null; + return playerToTeam.get(entity); } public void removeTeam(String teamName) { @@ -380,7 +383,8 @@ public final class Scoreboard { for (Entity entity : session.getEntityCache().getEntities().values()) { // This more complex logic is for the future to iterate over all entities, not just players if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) { - player.updateDisplayName(team, true); + player.updateDisplayName(team); + player.updateBedrockMetadata(); if (names.isEmpty()) { break; } @@ -396,7 +400,8 @@ public final class Scoreboard { for (Entity entity : session.getEntityCache().getEntities().values()) { if (entity instanceof PlayerEntity player) { Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername()); - player.updateDisplayName(playerTeam, true); + player.updateDisplayName(playerTeam); + player.updateBedrockMetadata(); } } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index d7840627f..34db4a048 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -65,6 +65,7 @@ public final class Team { if (entities.add(name)) { added.add(name); } + scoreboard.getPlayerToTeam().put(name, this); } if (added.isEmpty()) { @@ -93,6 +94,7 @@ public final class Team { if (entities.remove(name)) { removed.add(name); } + scoreboard.getPlayerToTeam().remove(name, this); } return removed; } From 25c2d30881ec2a0c7abf39be950db503303c2b03 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:31:26 -0500 Subject: [PATCH 30/38] Remove players from player team map on team remove --- .../main/java/org/geysermc/geyser/scoreboard/Scoreboard.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index 56e1e67f8..f97693a62 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -351,6 +351,9 @@ public final class Scoreboard { // We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly // With the player's lack of a team in visibility checks updateEntityNames(remove, remove.getEntities(), true); + for (String name : remove.getEntities()) { + playerToTeam.remove(name, remove); + } session.removeCommandEnum("Geyser_Teams", remove.getId()); } From 0bba18c726f5887765e194468a84c7ee4e9960fa Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 5 Feb 2023 13:29:28 -0500 Subject: [PATCH 31/38] Initial support for 1.19.60 Bedrock --- .../geysermc/geyser/network/GameProtocol.java | 6 + .../populator/BlockRegistryPopulator.java | 3 +- .../populator/ItemRegistryPopulator.java | 11 +- .../java/JavaUpdateRecipesTranslator.java | 15 +- .../bedrock/block_palette.1_19_60.nbt | Bin 0 -> 75100 bytes .../bedrock/creative_items.1_19_60.json | 5452 +++++++++++++++++ .../bedrock/runtime_item_states.1_19_60.json | 4682 ++++++++++++++ gradle/libs.versions.toml | 4 +- 8 files changed, 10159 insertions(+), 14 deletions(-) create mode 100644 core/src/main/resources/bedrock/block_palette.1_19_60.nbt create mode 100644 core/src/main/resources/bedrock/creative_items.1_19_60.json create mode 100644 core/src/main/resources/bedrock/runtime_item_states.1_19_60.json diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 6b46f8056..804187075 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.v545.Bedrock_v545; import com.nukkitx.protocol.bedrock.v554.Bedrock_v554; import com.nukkitx.protocol.bedrock.v557.Bedrock_v557; import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; +import com.nukkitx.protocol.bedrock.v567.Bedrock_v567; import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; @@ -73,6 +74,7 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() .minecraftVersion("1.19.50/1.19.51") .build()); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v567.V567_CODEC); } /** @@ -99,6 +101,10 @@ public final class GameProtocol { return session.getUpstream().getProtocolVersion() >= Bedrock_v560.V560_CODEC.getProtocolVersion(); } + public static boolean supports1_19_60(GeyserSession session) { + return session.getUpstream().getProtocolVersion() >= Bedrock_v567.V567_CODEC.getProtocolVersion(); + } + /** * Gets the {@link PacketCodec} for Minecraft: Java Edition. * diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index cbab03990..bac15377d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -29,9 +29,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; +import com.nukkitx.protocol.bedrock.v567.Bedrock_v567; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -76,6 +76,7 @@ public final class BlockRegistryPopulator { ImmutableMap, BiFunction> blockMappers = ImmutableMap., BiFunction>builder() .put(ObjectIntPair.of("1_19_20", Bedrock_v544.V544_CODEC.getProtocolVersion()), emptyMapper) .put(ObjectIntPair.of("1_19_50", Bedrock_v560.V560_CODEC.getProtocolVersion()), emptyMapper) + .put(ObjectIntPair.of("1_19_60", Bedrock_v567.V567_CODEC.getProtocolVersion()), emptyMapper) .build(); for (Map.Entry, BiFunction> palette : blockMappers.entrySet()) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 4b218aa7d..2d35c0f1d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -37,14 +37,10 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; -import it.unimi.dsi.fastutil.ints.*; -import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; -import com.nukkitx.protocol.bedrock.v534.Bedrock_v534; import com.nukkitx.protocol.bedrock.v544.Bedrock_v544; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; +import com.nukkitx.protocol.bedrock.v560.Bedrock_v560; +import com.nukkitx.protocol.bedrock.v567.Bedrock_v567; +import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.*; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; @@ -79,6 +75,7 @@ public class ItemRegistryPopulator { Map paletteVersions = new Object2ObjectOpenHashMap<>(); paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.V544_CODEC.getProtocolVersion(), Collections.emptyMap())); paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.V560_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.V567_CODEC.getProtocolVersion(), Collections.emptyMap())); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java index 90468a9cb..6159e9dd5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -46,6 +46,7 @@ import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; @@ -144,10 +145,16 @@ public class JavaUpdateRecipesTranslator extends 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 literal 0 HcmV?d00001 diff --git a/core/src/main/resources/bedrock/creative_items.1_19_60.json b/core/src/main/resources/bedrock/creative_items.1_19_60.json new file mode 100644 index 000000000..cee6d9332 --- /dev/null +++ b/core/src/main/resources/bedrock/creative_items.1_19_60.json @@ -0,0 +1,5452 @@ +{ + "items" : [ + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9871 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9872 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9873 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9874 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9875 + }, + { + "id" : "minecraft:planks", + "blockRuntimeId" : 9876 + }, + { + "id" : "minecraft:mangrove_planks", + "blockRuntimeId" : 1633 + }, + { + "id" : "minecraft:crimson_planks", + "blockRuntimeId" : 7465 + }, + { + "id" : "minecraft:warped_planks", + "blockRuntimeId" : 1606 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1868 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1869 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1870 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1871 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1872 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1873 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1880 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1875 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1876 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1874 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1877 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1881 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1878 + }, + { + "id" : "minecraft:cobblestone_wall", + "blockRuntimeId" : 1879 + }, + { + "id" : "minecraft:blackstone_wall", + "blockRuntimeId" : 5773 + }, + { + "id" : "minecraft:polished_blackstone_wall", + "blockRuntimeId" : 10562 + }, + { + "id" : "minecraft:polished_blackstone_brick_wall", + "blockRuntimeId" : 1657 + }, + { + "id" : "minecraft:cobbled_deepslate_wall", + "blockRuntimeId" : 12326 + }, + { + "id" : "minecraft:deepslate_tile_wall", + "blockRuntimeId" : 7702 + }, + { + "id" : "minecraft:polished_deepslate_wall", + "blockRuntimeId" : 12061 + }, + { + "id" : "minecraft:deepslate_brick_wall", + "blockRuntimeId" : 722 + }, + { + "id" : "minecraft:mud_brick_wall", + "blockRuntimeId" : 1416 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11608 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11609 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11610 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11611 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11612 + }, + { + "id" : "minecraft:fence", + "blockRuntimeId" : 11613 + }, + { + "id" : "minecraft:mangrove_fence", + "blockRuntimeId" : 10471 + }, + { + "id" : "minecraft:nether_brick_fence", + "blockRuntimeId" : 6137 + }, + { + "id" : "minecraft:crimson_fence", + "blockRuntimeId" : 12240 + }, + { + "id" : "minecraft:warped_fence", + "blockRuntimeId" : 8885 + }, + { + "id" : "minecraft:fence_gate", + "blockRuntimeId" : 79 + }, + { + "id" : "minecraft:spruce_fence_gate", + "blockRuntimeId" : 10422 + }, + { + "id" : "minecraft:birch_fence_gate", + "blockRuntimeId" : 5236 + }, + { + "id" : "minecraft:jungle_fence_gate", + "blockRuntimeId" : 8012 + }, + { + "id" : "minecraft:acacia_fence_gate", + "blockRuntimeId" : 11830 + }, + { + "id" : "minecraft:dark_oak_fence_gate", + "blockRuntimeId" : 6016 + }, + { + "id" : "minecraft:mangrove_fence_gate", + "blockRuntimeId" : 6472 + }, + { + "id" : "minecraft:crimson_fence_gate", + "blockRuntimeId" : 6892 + }, + { + "id" : "minecraft:warped_fence_gate", + "blockRuntimeId" : 8046 + }, + { + "id" : "minecraft:normal_stone_stairs", + "blockRuntimeId" : 927 + }, + { + "id" : "minecraft:stone_stairs", + "blockRuntimeId" : 5167 + }, + { + "id" : "minecraft:mossy_cobblestone_stairs", + "blockRuntimeId" : 5935 + }, + { + "id" : "minecraft:oak_stairs", + "blockRuntimeId" : 276 + }, + { + "id" : "minecraft:spruce_stairs", + "blockRuntimeId" : 131 + }, + { + "id" : "minecraft:birch_stairs", + "blockRuntimeId" : 10847 + }, + { + "id" : "minecraft:jungle_stairs", + "blockRuntimeId" : 10811 + }, + { + "id" : "minecraft:acacia_stairs", + "blockRuntimeId" : 10016 + }, + { + "id" : "minecraft:dark_oak_stairs", + "blockRuntimeId" : 7694 + }, + { + "id" : "minecraft:mangrove_stairs", + "blockRuntimeId" : 6442 + }, + { + "id" : "minecraft:stone_brick_stairs", + "blockRuntimeId" : 1617 + }, + { + "id" : "minecraft:mossy_stone_brick_stairs", + "blockRuntimeId" : 9299 + }, + { + "id" : "minecraft:sandstone_stairs", + "blockRuntimeId" : 5043 + }, + { + "id" : "minecraft:smooth_sandstone_stairs", + "blockRuntimeId" : 5086 + }, + { + "id" : "minecraft:red_sandstone_stairs", + "blockRuntimeId" : 7997 + }, + { + "id" : "minecraft:smooth_red_sandstone_stairs", + "blockRuntimeId" : 8193 + }, + { + "id" : "minecraft:granite_stairs", + "blockRuntimeId" : 4609 + }, + { + "id" : "minecraft:polished_granite_stairs", + "blockRuntimeId" : 5993 + }, + { + "id" : "minecraft:diorite_stairs", + "blockRuntimeId" : 6238 + }, + { + "id" : "minecraft:polished_diorite_stairs", + "blockRuntimeId" : 10552 + }, + { + "id" : "minecraft:andesite_stairs", + "blockRuntimeId" : 7955 + }, + { + "id" : "minecraft:polished_andesite_stairs", + "blockRuntimeId" : 10872 + }, + { + "id" : "minecraft:brick_stairs", + "blockRuntimeId" : 10368 + }, + { + "id" : "minecraft:nether_brick_stairs", + "blockRuntimeId" : 109 + }, + { + "id" : "minecraft:red_nether_brick_stairs", + "blockRuntimeId" : 10440 + }, + { + "id" : "minecraft:end_brick_stairs", + "blockRuntimeId" : 10206 + }, + { + "id" : "minecraft:quartz_stairs", + "blockRuntimeId" : 6998 + }, + { + "id" : "minecraft:smooth_quartz_stairs", + "blockRuntimeId" : 11944 + }, + { + "id" : "minecraft:purpur_stairs", + "blockRuntimeId" : 11999 + }, + { + "id" : "minecraft:prismarine_stairs", + "blockRuntimeId" : 11507 + }, + { + "id" : "minecraft:dark_prismarine_stairs", + "blockRuntimeId" : 11674 + }, + { + "id" : "minecraft:prismarine_bricks_stairs", + "blockRuntimeId" : 209 + }, + { + "id" : "minecraft:crimson_stairs", + "blockRuntimeId" : 10104 + }, + { + "id" : "minecraft:warped_stairs", + "blockRuntimeId" : 5177 + }, + { + "id" : "minecraft:blackstone_stairs", + "blockRuntimeId" : 10863 + }, + { + "id" : "minecraft:polished_blackstone_stairs", + "blockRuntimeId" : 6144 + }, + { + "id" : "minecraft:polished_blackstone_brick_stairs", + "blockRuntimeId" : 6324 + }, + { + "id" : "minecraft:cut_copper_stairs", + "blockRuntimeId" : 6451 + }, + { + "id" : "minecraft:exposed_cut_copper_stairs", + "blockRuntimeId" : 6434 + }, + { + "id" : "minecraft:weathered_cut_copper_stairs", + "blockRuntimeId" : 6152 + }, + { + "id" : "minecraft:oxidized_cut_copper_stairs", + "blockRuntimeId" : 644 + }, + { + "id" : "minecraft:waxed_cut_copper_stairs", + "blockRuntimeId" : 686 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_stairs", + "blockRuntimeId" : 5745 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_stairs", + "blockRuntimeId" : 9983 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_stairs", + "blockRuntimeId" : 8872 + }, + { + "id" : "minecraft:cobbled_deepslate_stairs", + "blockRuntimeId" : 150 + }, + { + "id" : "minecraft:deepslate_tile_stairs", + "blockRuntimeId" : 6884 + }, + { + "id" : "minecraft:polished_deepslate_stairs", + "blockRuntimeId" : 585 + }, + { + "id" : "minecraft:deepslate_brick_stairs", + "blockRuntimeId" : 11666 + }, + { + "id" : "minecraft:mud_brick_stairs", + "blockRuntimeId" : 8169 + }, + { + "id" : "minecraft:wooden_door" + }, + { + "id" : "minecraft:spruce_door" + }, + { + "id" : "minecraft:birch_door" + }, + { + "id" : "minecraft:jungle_door" + }, + { + "id" : "minecraft:acacia_door" + }, + { + "id" : "minecraft:dark_oak_door" + }, + { + "id" : "minecraft:mangrove_door" + }, + { + "id" : "minecraft:iron_door" + }, + { + "id" : "minecraft:crimson_door" + }, + { + "id" : "minecraft:warped_door" + }, + { + "id" : "minecraft:trapdoor", + "blockRuntimeId" : 232 + }, + { + "id" : "minecraft:spruce_trapdoor", + "blockRuntimeId" : 10390 + }, + { + "id" : "minecraft:birch_trapdoor", + "blockRuntimeId" : 10488 + }, + { + "id" : "minecraft:jungle_trapdoor", + "blockRuntimeId" : 8028 + }, + { + "id" : "minecraft:acacia_trapdoor", + "blockRuntimeId" : 8236 + }, + { + "id" : "minecraft:dark_oak_trapdoor", + "blockRuntimeId" : 11746 + }, + { + "id" : "minecraft:mangrove_trapdoor", + "blockRuntimeId" : 6332 + }, + { + "id" : "minecraft:iron_trapdoor", + "blockRuntimeId" : 612 + }, + { + "id" : "minecraft:crimson_trapdoor", + "blockRuntimeId" : 6180 + }, + { + "id" : "minecraft:warped_trapdoor", + "blockRuntimeId" : 6964 + }, + { + "id" : "minecraft:iron_bars", + "blockRuntimeId" : 7032 + }, + { + "id" : "minecraft:glass", + "blockRuntimeId" : 9980 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1819 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1827 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1826 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1834 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1831 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1833 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1820 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1823 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1824 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1832 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1828 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1822 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1830 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1829 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1821 + }, + { + "id" : "minecraft:stained_glass", + "blockRuntimeId" : 1825 + }, + { + "id" : "minecraft:tinted_glass", + "blockRuntimeId" : 9391 + }, + { + "id" : "minecraft:glass_pane", + "blockRuntimeId" : 7864 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7467 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7475 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7474 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7482 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7479 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7481 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7468 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7471 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7472 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7480 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7476 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7470 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7478 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7477 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7469 + }, + { + "id" : "minecraft:stained_glass_pane", + "blockRuntimeId" : 7473 + }, + { + "id" : "minecraft:ladder", + "blockRuntimeId" : 12507 + }, + { + "id" : "minecraft:scaffolding", + "blockRuntimeId" : 5027 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6115 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8470 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6118 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8441 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7917 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7918 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7919 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7920 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7921 + }, + { + "id" : "minecraft:wooden_slab", + "blockRuntimeId" : 7922 + }, + { + "id" : "minecraft:mangrove_slab", + "blockRuntimeId" : 1835 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6120 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8468 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6116 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8471 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8442 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8436 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8472 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8453 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8458 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8459 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8456 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8457 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8455 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8454 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6119 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6122 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8443 + }, + { + "id" : "minecraft:stone_block_slab3", + "blockRuntimeId" : 8452 + }, + { + "id" : "minecraft:stone_block_slab", + "blockRuntimeId" : 6121 + }, + { + "id" : "minecraft:stone_block_slab4", + "blockRuntimeId" : 8469 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8437 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8438 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8439 + }, + { + "id" : "minecraft:stone_block_slab2", + "blockRuntimeId" : 8440 + }, + { + "id" : "minecraft:crimson_slab", + "blockRuntimeId" : 9316 + }, + { + "id" : "minecraft:warped_slab", + "blockRuntimeId" : 10320 + }, + { + "id" : "minecraft:blackstone_slab", + "blockRuntimeId" : 1596 + }, + { + "id" : "minecraft:polished_blackstone_slab", + "blockRuntimeId" : 9818 + }, + { + "id" : "minecraft:polished_blackstone_brick_slab", + "blockRuntimeId" : 6037 + }, + { + "id" : "minecraft:cut_copper_slab", + "blockRuntimeId" : 7866 + }, + { + "id" : "minecraft:exposed_cut_copper_slab", + "blockRuntimeId" : 10438 + }, + { + "id" : "minecraft:weathered_cut_copper_slab", + "blockRuntimeId" : 9853 + }, + { + "id" : "minecraft:oxidized_cut_copper_slab", + "blockRuntimeId" : 7929 + }, + { + "id" : "minecraft:waxed_cut_copper_slab", + "blockRuntimeId" : 12059 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper_slab", + "blockRuntimeId" : 252 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper_slab", + "blockRuntimeId" : 10383 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper_slab", + "blockRuntimeId" : 1386 + }, + { + "id" : "minecraft:cobbled_deepslate_slab", + "blockRuntimeId" : 11554 + }, + { + "id" : "minecraft:polished_deepslate_slab", + "blockRuntimeId" : 291 + }, + { + "id" : "minecraft:deepslate_tile_slab", + "blockRuntimeId" : 6138 + }, + { + "id" : "minecraft:deepslate_brick_slab", + "blockRuntimeId" : 5175 + }, + { + "id" : "minecraft:mud_brick_slab", + "blockRuntimeId" : 5753 + }, + { + "id" : "minecraft:brick_block", + "blockRuntimeId" : 6996 + }, + { + "id" : "minecraft:chiseled_nether_bricks", + "blockRuntimeId" : 11493 + }, + { + "id" : "minecraft:cracked_nether_bricks", + "blockRuntimeId" : 6399 + }, + { + "id" : "minecraft:quartz_bricks", + "blockRuntimeId" : 10175 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10385 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10386 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10387 + }, + { + "id" : "minecraft:stonebrick", + "blockRuntimeId" : 10388 + }, + { + "id" : "minecraft:end_bricks", + "blockRuntimeId" : 284 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9903 + }, + { + "id" : "minecraft:polished_blackstone_bricks", + "blockRuntimeId" : 6911 + }, + { + "id" : "minecraft:cracked_polished_blackstone_bricks", + "blockRuntimeId" : 11442 + }, + { + "id" : "minecraft:gilded_blackstone", + "blockRuntimeId" : 6433 + }, + { + "id" : "minecraft:chiseled_polished_blackstone", + "blockRuntimeId" : 7693 + }, + { + "id" : "minecraft:deepslate_tiles", + "blockRuntimeId" : 6428 + }, + { + "id" : "minecraft:cracked_deepslate_tiles", + "blockRuntimeId" : 6003 + }, + { + "id" : "minecraft:deepslate_bricks", + "blockRuntimeId" : 8111 + }, + { + "id" : "minecraft:cracked_deepslate_bricks", + "blockRuntimeId" : 8011 + }, + { + "id" : "minecraft:chiseled_deepslate", + "blockRuntimeId" : 7865 + }, + { + "id" : "minecraft:cobblestone", + "blockRuntimeId" : 5074 + }, + { + "id" : "minecraft:mossy_cobblestone", + "blockRuntimeId" : 255 + }, + { + "id" : "minecraft:cobbled_deepslate", + "blockRuntimeId" : 10508 + }, + { + "id" : "minecraft:smooth_stone", + "blockRuntimeId" : 6429 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5112 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5113 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5114 + }, + { + "id" : "minecraft:sandstone", + "blockRuntimeId" : 5115 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10418 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10419 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10420 + }, + { + "id" : "minecraft:red_sandstone", + "blockRuntimeId" : 10421 + }, + { + "id" : "minecraft:coal_block", + "blockRuntimeId" : 8045 + }, + { + "id" : "minecraft:dried_kelp_block", + "blockRuntimeId" : 12223 + }, + { + "id" : "minecraft:gold_block", + "blockRuntimeId" : 326 + }, + { + "id" : "minecraft:iron_block", + "blockRuntimeId" : 12506 + }, + { + "id" : "minecraft:copper_block", + "blockRuntimeId" : 6882 + }, + { + "id" : "minecraft:exposed_copper", + "blockRuntimeId" : 886 + }, + { + "id" : "minecraft:weathered_copper", + "blockRuntimeId" : 12490 + }, + { + "id" : "minecraft:oxidized_copper", + "blockRuntimeId" : 5009 + }, + { + "id" : "minecraft:waxed_copper", + "blockRuntimeId" : 11978 + }, + { + "id" : "minecraft:waxed_exposed_copper", + "blockRuntimeId" : 1372 + }, + { + "id" : "minecraft:waxed_weathered_copper", + "blockRuntimeId" : 1385 + }, + { + "id" : "minecraft:waxed_oxidized_copper", + "blockRuntimeId" : 11786 + }, + { + "id" : "minecraft:cut_copper", + "blockRuntimeId" : 6920 + }, + { + "id" : "minecraft:exposed_cut_copper", + "blockRuntimeId" : 9982 + }, + { + "id" : "minecraft:weathered_cut_copper", + "blockRuntimeId" : 11425 + }, + { + "id" : "minecraft:oxidized_cut_copper", + "blockRuntimeId" : 8125 + }, + { + "id" : "minecraft:waxed_cut_copper", + "blockRuntimeId" : 11537 + }, + { + "id" : "minecraft:waxed_exposed_cut_copper", + "blockRuntimeId" : 5268 + }, + { + "id" : "minecraft:waxed_weathered_cut_copper", + "blockRuntimeId" : 7466 + }, + { + "id" : "minecraft:waxed_oxidized_cut_copper", + "blockRuntimeId" : 217 + }, + { + "id" : "minecraft:emerald_block", + "blockRuntimeId" : 1845 + }, + { + "id" : "minecraft:diamond_block", + "blockRuntimeId" : 275 + }, + { + "id" : "minecraft:lapis_block", + "blockRuntimeId" : 6131 + }, + { + "id" : "minecraft:raw_iron_block", + "blockRuntimeId" : 12505 + }, + { + "id" : "minecraft:raw_copper_block", + "blockRuntimeId" : 7916 + }, + { + "id" : "minecraft:raw_gold_block", + "blockRuntimeId" : 654 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5155 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5157 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5156 + }, + { + "id" : "minecraft:quartz_block", + "blockRuntimeId" : 5158 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9901 + }, + { + "id" : "minecraft:prismarine", + "blockRuntimeId" : 9902 + }, + { + "id" : "minecraft:slime", + "blockRuntimeId" : 6078 + }, + { + "id" : "minecraft:honey_block", + "blockRuntimeId" : 1578 + }, + { + "id" : "minecraft:honeycomb_block", + "blockRuntimeId" : 6323 + }, + { + "id" : "minecraft:hay_block", + "blockRuntimeId" : 1373 + }, + { + "id" : "minecraft:bone_block", + "blockRuntimeId" : 6079 + }, + { + "id" : "minecraft:nether_brick", + "blockRuntimeId" : 11516 + }, + { + "id" : "minecraft:red_nether_brick", + "blockRuntimeId" : 149 + }, + { + "id" : "minecraft:netherite_block", + "blockRuntimeId" : 5234 + }, + { + "id" : "minecraft:lodestone", + "blockRuntimeId" : 12503 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4146 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4154 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4153 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4161 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4158 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4160 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4147 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4150 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4151 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4159 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4155 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4149 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4157 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4156 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4148 + }, + { + "id" : "minecraft:wool", + "blockRuntimeId" : 4152 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1635 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1643 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1642 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1650 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1647 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1649 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1636 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1639 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1640 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1648 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1644 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1638 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1646 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1645 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1637 + }, + { + "id" : "minecraft:carpet", + "blockRuntimeId" : 1641 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10088 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10096 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10095 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10103 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10100 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10102 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10089 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10092 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10093 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10101 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10097 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10091 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10099 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10098 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10090 + }, + { + "id" : "minecraft:concrete_powder", + "blockRuntimeId" : 10094 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1338 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1346 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1345 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1353 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1350 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1352 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1339 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1342 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1343 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1351 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1347 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1341 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1349 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1348 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1340 + }, + { + "id" : "minecraft:concrete", + "blockRuntimeId" : 1344 + }, + { + "id" : "minecraft:clay", + "blockRuntimeId" : 10968 + }, + { + "id" : "minecraft:hardened_clay", + "blockRuntimeId" : 935 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9992 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10000 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9999 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10007 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10004 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10006 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9993 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9996 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9997 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10005 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10001 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9995 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10003 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 10002 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9994 + }, + { + "id" : "minecraft:stained_hardened_clay", + "blockRuntimeId" : 9998 + }, + { + "id" : "minecraft:white_glazed_terracotta", + "blockRuntimeId" : 8220 + }, + { + "id" : "minecraft:silver_glazed_terracotta", + "blockRuntimeId" : 4603 + }, + { + "id" : "minecraft:gray_glazed_terracotta", + "blockRuntimeId" : 12497 + }, + { + "id" : "minecraft:black_glazed_terracotta", + "blockRuntimeId" : 8866 + }, + { + "id" : "minecraft:brown_glazed_terracotta", + "blockRuntimeId" : 5003 + }, + { + "id" : "minecraft:red_glazed_terracotta", + "blockRuntimeId" : 6010 + }, + { + "id" : "minecraft:orange_glazed_terracotta", + "blockRuntimeId" : 1837 + }, + { + "id" : "minecraft:yellow_glazed_terracotta", + "blockRuntimeId" : 1599 + }, + { + "id" : "minecraft:lime_glazed_terracotta", + "blockRuntimeId" : 226 + }, + { + "id" : "minecraft:green_glazed_terracotta", + "blockRuntimeId" : 10448 + }, + { + "id" : "minecraft:cyan_glazed_terracotta", + "blockRuntimeId" : 8005 + }, + { + "id" : "minecraft:light_blue_glazed_terracotta", + "blockRuntimeId" : 8118 + }, + { + "id" : "minecraft:blue_glazed_terracotta", + "blockRuntimeId" : 8112 + }, + { + "id" : "minecraft:purple_glazed_terracotta", + "blockRuntimeId" : 10855 + }, + { + "id" : "minecraft:magenta_glazed_terracotta", + "blockRuntimeId" : 1651 + }, + { + "id" : "minecraft:pink_glazed_terracotta", + "blockRuntimeId" : 10377 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 11958 + }, + { + "id" : "minecraft:purpur_block", + "blockRuntimeId" : 11960 + }, + { + "id" : "minecraft:packed_mud", + "blockRuntimeId" : 286 + }, + { + "id" : "minecraft:mud_bricks", + "blockRuntimeId" : 10727 + }, + { + "id" : "minecraft:nether_wart_block", + "blockRuntimeId" : 6140 + }, + { + "id" : "minecraft:warped_wart_block", + "blockRuntimeId" : 9321 + }, + { + "id" : "minecraft:shroomlight", + "blockRuntimeId" : 7676 + }, + { + "id" : "minecraft:crimson_nylium", + "blockRuntimeId" : 6034 + }, + { + "id" : "minecraft:warped_nylium", + "blockRuntimeId" : 10173 + }, + { + "id" : "minecraft:basalt", + "blockRuntimeId" : 6196 + }, + { + "id" : "minecraft:polished_basalt", + "blockRuntimeId" : 24 + }, + { + "id" : "minecraft:smooth_basalt", + "blockRuntimeId" : 1843 + }, + { + "id" : "minecraft:soul_soil", + "blockRuntimeId" : 8478 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 8399 + }, + { + "id" : "minecraft:dirt", + "blockRuntimeId" : 8400 + }, + { + "id" : "minecraft:farmland", + "blockRuntimeId" : 5755 + }, + { + "id" : "minecraft:grass", + "blockRuntimeId" : 10819 + }, + { + "id" : "minecraft:grass_path", + "blockRuntimeId" : 12325 + }, + { + "id" : "minecraft:podzol", + "blockRuntimeId" : 6881 + }, + { + "id" : "minecraft:mycelium", + "blockRuntimeId" : 5142 + }, + { + "id" : "minecraft:mud", + "blockRuntimeId" : 10522 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1331 + }, + { + "id" : "minecraft:iron_ore", + "blockRuntimeId" : 6921 + }, + { + "id" : "minecraft:gold_ore", + "blockRuntimeId" : 1598 + }, + { + "id" : "minecraft:diamond_ore", + "blockRuntimeId" : 6208 + }, + { + "id" : "minecraft:lapis_ore", + "blockRuntimeId" : 11943 + }, + { + "id" : "minecraft:redstone_ore", + "blockRuntimeId" : 6134 + }, + { + "id" : "minecraft:coal_ore", + "blockRuntimeId" : 6132 + }, + { + "id" : "minecraft:copper_ore", + "blockRuntimeId" : 5010 + }, + { + "id" : "minecraft:emerald_ore", + "blockRuntimeId" : 11591 + }, + { + "id" : "minecraft:quartz_ore", + "blockRuntimeId" : 6348 + }, + { + "id" : "minecraft:nether_gold_ore", + "blockRuntimeId" : 27 + }, + { + "id" : "minecraft:ancient_debris", + "blockRuntimeId" : 9923 + }, + { + "id" : "minecraft:deepslate_iron_ore", + "blockRuntimeId" : 11517 + }, + { + "id" : "minecraft:deepslate_gold_ore", + "blockRuntimeId" : 9922 + }, + { + "id" : "minecraft:deepslate_diamond_ore", + "blockRuntimeId" : 12282 + }, + { + "id" : "minecraft:deepslate_lapis_ore", + "blockRuntimeId" : 11506 + }, + { + "id" : "minecraft:deepslate_redstone_ore", + "blockRuntimeId" : 10454 + }, + { + "id" : "minecraft:deepslate_emerald_ore", + "blockRuntimeId" : 10174 + }, + { + "id" : "minecraft:deepslate_coal_ore", + "blockRuntimeId" : 11424 + }, + { + "id" : "minecraft:deepslate_copper_ore", + "blockRuntimeId" : 108 + }, + { + "id" : "minecraft:gravel", + "blockRuntimeId" : 12532 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1332 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1334 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1336 + }, + { + "id" : "minecraft:blackstone", + "blockRuntimeId" : 11829 + }, + { + "id" : "minecraft:deepslate", + "blockRuntimeId" : 256 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1333 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1335 + }, + { + "id" : "minecraft:stone", + "blockRuntimeId" : 1337 + }, + { + "id" : "minecraft:polished_blackstone", + "blockRuntimeId" : 5141 + }, + { + "id" : "minecraft:polished_deepslate", + "blockRuntimeId" : 11998 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 6040 + }, + { + "id" : "minecraft:sand", + "blockRuntimeId" : 6041 + }, + { + "id" : "minecraft:cactus", + "blockRuntimeId" : 10830 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10510 + }, + { + "id" : "minecraft:stripped_oak_log", + "blockRuntimeId" : 11787 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10511 + }, + { + "id" : "minecraft:stripped_spruce_log", + "blockRuntimeId" : 10112 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10512 + }, + { + "id" : "minecraft:stripped_birch_log", + "blockRuntimeId" : 9388 + }, + { + "id" : "minecraft:log", + "blockRuntimeId" : 10513 + }, + { + "id" : "minecraft:stripped_jungle_log", + "blockRuntimeId" : 1320 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 5673 + }, + { + "id" : "minecraft:stripped_acacia_log", + "blockRuntimeId" : 8880 + }, + { + "id" : "minecraft:log2", + "blockRuntimeId" : 5674 + }, + { + "id" : "minecraft:stripped_dark_oak_log", + "blockRuntimeId" : 219 + }, + { + "id" : "minecraft:mangrove_log", + "blockRuntimeId" : 641 + }, + { + "id" : "minecraft:stripped_mangrove_log", + "blockRuntimeId" : 12529 + }, + { + "id" : "minecraft:crimson_stem", + "blockRuntimeId" : 9313 + }, + { + "id" : "minecraft:stripped_crimson_stem", + "blockRuntimeId" : 10792 + }, + { + "id" : "minecraft:warped_stem", + "blockRuntimeId" : 10322 + }, + { + "id" : "minecraft:stripped_warped_stem", + "blockRuntimeId" : 11644 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4162 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4168 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4163 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4169 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4164 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4170 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4165 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4171 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4166 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4172 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4167 + }, + { + "id" : "minecraft:wood", + "blockRuntimeId" : 4173 + }, + { + "id" : "minecraft:mangrove_wood", + "blockRuntimeId" : 6004 + }, + { + "id" : "minecraft:stripped_mangrove_wood", + "blockRuntimeId" : 6074 + }, + { + "id" : "minecraft:crimson_hyphae", + "blockRuntimeId" : 6141 + }, + { + "id" : "minecraft:stripped_crimson_hyphae", + "blockRuntimeId" : 10335 + }, + { + "id" : "minecraft:warped_hyphae", + "blockRuntimeId" : 9318 + }, + { + "id" : "minecraft:stripped_warped_hyphae", + "blockRuntimeId" : 8226 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9906 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9907 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9908 + }, + { + "id" : "minecraft:leaves", + "blockRuntimeId" : 9909 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 6200 + }, + { + "id" : "minecraft:leaves2", + "blockRuntimeId" : 6201 + }, + { + "id" : "minecraft:mangrove_leaves", + "blockRuntimeId" : 10504 + }, + { + "id" : "minecraft:azalea_leaves", + "blockRuntimeId" : 11954 + }, + { + "id" : "minecraft:azalea_leaves_flowered", + "blockRuntimeId" : 10163 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1390 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1391 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1392 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1393 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1394 + }, + { + "id" : "minecraft:sapling", + "blockRuntimeId" : 1395 + }, + { + "id" : "minecraft:mangrove_propagule", + "blockRuntimeId" : 10820 + }, + { + "id" : "minecraft:bee_nest", + "blockRuntimeId" : 8402 + }, + { + "id" : "minecraft:wheat_seeds" + }, + { + "id" : "minecraft:pumpkin_seeds" + }, + { + "id" : "minecraft:melon_seeds" + }, + { + "id" : "minecraft:beetroot_seeds" + }, + { + "id" : "minecraft:wheat" + }, + { + "id" : "minecraft:beetroot" + }, + { + "id" : "minecraft:potato" + }, + { + "id" : "minecraft:poisonous_potato" + }, + { + "id" : "minecraft:carrot" + }, + { + "id" : "minecraft:golden_carrot" + }, + { + "id" : "minecraft:apple" + }, + { + "id" : "minecraft:golden_apple" + }, + { + "id" : "minecraft:enchanted_golden_apple" + }, + { + "id" : "minecraft:melon_block", + "blockRuntimeId" : 685 + }, + { + "id" : "minecraft:melon_slice" + }, + { + "id" : "minecraft:glistering_melon_slice" + }, + { + "id" : "minecraft:sweet_berries" + }, + { + "id" : "minecraft:glow_berries" + }, + { + "id" : "minecraft:pumpkin", + "blockRuntimeId" : 6424 + }, + { + "id" : "minecraft:carved_pumpkin", + "blockRuntimeId" : 11622 + }, + { + "id" : "minecraft:lit_pumpkin", + "blockRuntimeId" : 10523 + }, + { + "id" : "minecraft:honeycomb" + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 1615 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8102 + }, + { + "id" : "minecraft:tallgrass", + "blockRuntimeId" : 1614 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8101 + }, + { + "id" : "minecraft:nether_sprouts" + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10328 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10326 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10327 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10325 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10329 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10333 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10331 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10332 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10330 + }, + { + "id" : "minecraft:coral", + "blockRuntimeId" : 10334 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6463 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6461 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6462 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6460 + }, + { + "id" : "minecraft:coral_fan", + "blockRuntimeId" : 6464 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 72 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 70 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 71 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 69 + }, + { + "id" : "minecraft:coral_fan_dead", + "blockRuntimeId" : 73 + }, + { + "id" : "minecraft:crimson_roots", + "blockRuntimeId" : 11817 + }, + { + "id" : "minecraft:warped_roots", + "blockRuntimeId" : 6209 + }, + { + "id" : "minecraft:yellow_flower", + "blockRuntimeId" : 593 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5075 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5076 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5077 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5078 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5079 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5080 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5081 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5082 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5083 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5084 + }, + { + "id" : "minecraft:red_flower", + "blockRuntimeId" : 5085 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8099 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8100 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8103 + }, + { + "id" : "minecraft:double_plant", + "blockRuntimeId" : 8104 + }, + { + "id" : "minecraft:wither_rose", + "blockRuntimeId" : 9981 + }, + { + "id" : "minecraft:white_dye" + }, + { + "id" : "minecraft:light_gray_dye" + }, + { + "id" : "minecraft:gray_dye" + }, + { + "id" : "minecraft:black_dye" + }, + { + "id" : "minecraft:brown_dye" + }, + { + "id" : "minecraft:red_dye" + }, + { + "id" : "minecraft:orange_dye" + }, + { + "id" : "minecraft:yellow_dye" + }, + { + "id" : "minecraft:lime_dye" + }, + { + "id" : "minecraft:green_dye" + }, + { + "id" : "minecraft:cyan_dye" + }, + { + "id" : "minecraft:light_blue_dye" + }, + { + "id" : "minecraft:blue_dye" + }, + { + "id" : "minecraft:purple_dye" + }, + { + "id" : "minecraft:magenta_dye" + }, + { + "id" : "minecraft:pink_dye" + }, + { + "id" : "minecraft:ink_sac" + }, + { + "id" : "minecraft:glow_ink_sac" + }, + { + "id" : "minecraft:cocoa_beans" + }, + { + "id" : "minecraft:lapis_lazuli" + }, + { + "id" : "minecraft:bone_meal" + }, + { + "id" : "minecraft:vine", + "blockRuntimeId" : 1580 + }, + { + "id" : "minecraft:weeping_vines", + "blockRuntimeId" : 8126 + }, + { + "id" : "minecraft:twisting_vines", + "blockRuntimeId" : 8339 + }, + { + "id" : "minecraft:waterlily", + "blockRuntimeId" : 1844 + }, + { + "id" : "minecraft:seagrass", + "blockRuntimeId" : 249 + }, + { + "id" : "minecraft:kelp" + }, + { + "id" : "minecraft:deadbush", + "blockRuntimeId" : 6908 + }, + { + "id" : "minecraft:bamboo", + "blockRuntimeId" : 5143 + }, + { + "id" : "minecraft:snow", + "blockRuntimeId" : 6039 + }, + { + "id" : "minecraft:ice", + "blockRuntimeId" : 10527 + }, + { + "id" : "minecraft:packed_ice", + "blockRuntimeId" : 285 + }, + { + "id" : "minecraft:blue_ice", + "blockRuntimeId" : 10871 + }, + { + "id" : "minecraft:snow_layer", + "blockRuntimeId" : 158 + }, + { + "id" : "minecraft:pointed_dripstone", + "blockRuntimeId" : 11660 + }, + { + "id" : "minecraft:dripstone_block", + "blockRuntimeId" : 1579 + }, + { + "id" : "minecraft:moss_carpet", + "blockRuntimeId" : 289 + }, + { + "id" : "minecraft:moss_block", + "blockRuntimeId" : 10376 + }, + { + "id" : "minecraft:dirt_with_roots", + "blockRuntimeId" : 8044 + }, + { + "id" : "minecraft:hanging_roots", + "blockRuntimeId" : 208 + }, + { + "id" : "minecraft:mangrove_roots", + "blockRuntimeId" : 9991 + }, + { + "id" : "minecraft:muddy_mangrove_roots", + "blockRuntimeId" : 636 + }, + { + "id" : "minecraft:big_dripleaf", + "blockRuntimeId" : 9396 + }, + { + "id" : "minecraft:small_dripleaf_block", + "blockRuntimeId" : 6167 + }, + { + "id" : "minecraft:spore_blossom", + "blockRuntimeId" : 11556 + }, + { + "id" : "minecraft:azalea", + "blockRuntimeId" : 10726 + }, + { + "id" : "minecraft:flowering_azalea", + "blockRuntimeId" : 8124 + }, + { + "id" : "minecraft:glow_lichen", + "blockRuntimeId" : 8332 + }, + { + "id" : "minecraft:amethyst_block", + "blockRuntimeId" : 325 + }, + { + "id" : "minecraft:budding_amethyst", + "blockRuntimeId" : 10846 + }, + { + "id" : "minecraft:amethyst_cluster", + "blockRuntimeId" : 12054 + }, + { + "id" : "minecraft:large_amethyst_bud", + "blockRuntimeId" : 6959 + }, + { + "id" : "minecraft:medium_amethyst_bud", + "blockRuntimeId" : 6223 + }, + { + "id" : "minecraft:small_amethyst_bud", + "blockRuntimeId" : 595 + }, + { + "id" : "minecraft:tuff", + "blockRuntimeId" : 640 + }, + { + "id" : "minecraft:calcite", + "blockRuntimeId" : 218 + }, + { + "id" : "minecraft:chicken" + }, + { + "id" : "minecraft:porkchop" + }, + { + "id" : "minecraft:beef" + }, + { + "id" : "minecraft:mutton" + }, + { + "id" : "minecraft:rabbit" + }, + { + "id" : "minecraft:cod" + }, + { + "id" : "minecraft:salmon" + }, + { + "id" : "minecraft:tropical_fish" + }, + { + "id" : "minecraft:pufferfish" + }, + { + "id" : "minecraft:brown_mushroom", + "blockRuntimeId" : 5002 + }, + { + "id" : "minecraft:red_mushroom", + "blockRuntimeId" : 6432 + }, + { + "id" : "minecraft:crimson_fungus", + "blockRuntimeId" : 11997 + }, + { + "id" : "minecraft:warped_fungus", + "blockRuntimeId" : 290 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11606 + }, + { + "id" : "minecraft:red_mushroom_block", + "blockRuntimeId" : 5070 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11607 + }, + { + "id" : "minecraft:brown_mushroom_block", + "blockRuntimeId" : 11592 + }, + { + "id" : "minecraft:egg" + }, + { + "id" : "minecraft:sugar_cane" + }, + { + "id" : "minecraft:sugar" + }, + { + "id" : "minecraft:rotten_flesh" + }, + { + "id" : "minecraft:bone" + }, + { + "id" : "minecraft:web", + "blockRuntimeId" : 10551 + }, + { + "id" : "minecraft:spider_eye" + }, + { + "id" : "minecraft:mob_spawner", + "blockRuntimeId" : 694 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5987 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5988 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5989 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5990 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5991 + }, + { + "id" : "minecraft:monster_egg", + "blockRuntimeId" : 5992 + }, + { + "id" : "minecraft:infested_deepslate", + "blockRuntimeId" : 6872 + }, + { + "id" : "minecraft:dragon_egg", + "blockRuntimeId" : 11515 + }, + { + "id" : "minecraft:turtle_egg", + "blockRuntimeId" : 12241 + }, + { + "id" : "minecraft:frog_spawn", + "blockRuntimeId" : 6246 + }, + { + "id" : "minecraft:pearlescent_froglight", + "blockRuntimeId" : 10259 + }, + { + "id" : "minecraft:verdant_froglight", + "blockRuntimeId" : 10317 + }, + { + "id" : "minecraft:ochre_froglight", + "blockRuntimeId" : 4582 + }, + { + "id" : "minecraft:chicken_spawn_egg" + }, + { + "id" : "minecraft:bee_spawn_egg" + }, + { + "id" : "minecraft:cow_spawn_egg" + }, + { + "id" : "minecraft:pig_spawn_egg" + }, + { + "id" : "minecraft:sheep_spawn_egg" + }, + { + "id" : "minecraft:wolf_spawn_egg" + }, + { + "id" : "minecraft:polar_bear_spawn_egg" + }, + { + "id" : "minecraft:ocelot_spawn_egg" + }, + { + "id" : "minecraft:cat_spawn_egg" + }, + { + "id" : "minecraft:mooshroom_spawn_egg" + }, + { + "id" : "minecraft:bat_spawn_egg" + }, + { + "id" : "minecraft:parrot_spawn_egg" + }, + { + "id" : "minecraft:rabbit_spawn_egg" + }, + { + "id" : "minecraft:llama_spawn_egg" + }, + { + "id" : "minecraft:horse_spawn_egg" + }, + { + "id" : "minecraft:donkey_spawn_egg" + }, + { + "id" : "minecraft:mule_spawn_egg" + }, + { + "id" : "minecraft:skeleton_horse_spawn_egg" + }, + { + "id" : "minecraft:zombie_horse_spawn_egg" + }, + { + "id" : "minecraft:tropical_fish_spawn_egg" + }, + { + "id" : "minecraft:cod_spawn_egg" + }, + { + "id" : "minecraft:pufferfish_spawn_egg" + }, + { + "id" : "minecraft:salmon_spawn_egg" + }, + { + "id" : "minecraft:dolphin_spawn_egg" + }, + { + "id" : "minecraft:turtle_spawn_egg" + }, + { + "id" : "minecraft:panda_spawn_egg" + }, + { + "id" : "minecraft:fox_spawn_egg" + }, + { + "id" : "minecraft:creeper_spawn_egg" + }, + { + "id" : "minecraft:enderman_spawn_egg" + }, + { + "id" : "minecraft:silverfish_spawn_egg" + }, + { + "id" : "minecraft:skeleton_spawn_egg" + }, + { + "id" : "minecraft:wither_skeleton_spawn_egg" + }, + { + "id" : "minecraft:stray_spawn_egg" + }, + { + "id" : "minecraft:slime_spawn_egg" + }, + { + "id" : "minecraft:spider_spawn_egg" + }, + { + "id" : "minecraft:zombie_spawn_egg" + }, + { + "id" : "minecraft:zombie_pigman_spawn_egg" + }, + { + "id" : "minecraft:husk_spawn_egg" + }, + { + "id" : "minecraft:drowned_spawn_egg" + }, + { + "id" : "minecraft:squid_spawn_egg" + }, + { + "id" : "minecraft:glow_squid_spawn_egg" + }, + { + "id" : "minecraft:cave_spider_spawn_egg" + }, + { + "id" : "minecraft:witch_spawn_egg" + }, + { + "id" : "minecraft:guardian_spawn_egg" + }, + { + "id" : "minecraft:elder_guardian_spawn_egg" + }, + { + "id" : "minecraft:endermite_spawn_egg" + }, + { + "id" : "minecraft:magma_cube_spawn_egg" + }, + { + "id" : "minecraft:strider_spawn_egg" + }, + { + "id" : "minecraft:hoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_spawn_egg" + }, + { + "id" : "minecraft:zoglin_spawn_egg" + }, + { + "id" : "minecraft:piglin_brute_spawn_egg" + }, + { + "id" : "minecraft:goat_spawn_egg" + }, + { + "id" : "minecraft:axolotl_spawn_egg" + }, + { + "id" : "minecraft:warden_spawn_egg" + }, + { + "id" : "minecraft:allay_spawn_egg" + }, + { + "id" : "minecraft:frog_spawn_egg" + }, + { + "id" : "minecraft:tadpole_spawn_egg" + }, + { + "id" : "minecraft:trader_llama_spawn_egg" + }, + { + "id" : "minecraft:ghast_spawn_egg" + }, + { + "id" : "minecraft:blaze_spawn_egg" + }, + { + "id" : "minecraft:shulker_spawn_egg" + }, + { + "id" : "minecraft:vindicator_spawn_egg" + }, + { + "id" : "minecraft:evoker_spawn_egg" + }, + { + "id" : "minecraft:vex_spawn_egg" + }, + { + "id" : "minecraft:villager_spawn_egg" + }, + { + "id" : "minecraft:wandering_trader_spawn_egg" + }, + { + "id" : "minecraft:zombie_villager_spawn_egg" + }, + { + "id" : "minecraft:phantom_spawn_egg" + }, + { + "id" : "minecraft:pillager_spawn_egg" + }, + { + "id" : "minecraft:ravager_spawn_egg" + }, + { + "id" : "minecraft:iron_golem_spawn_egg" + }, + { + "id" : "minecraft:snow_golem_spawn_egg" + }, + { + "id" : "minecraft:obsidian", + "blockRuntimeId" : 721 + }, + { + "id" : "minecraft:crying_obsidian", + "blockRuntimeId" : 10560 + }, + { + "id" : "minecraft:bedrock", + "blockRuntimeId" : 10861 + }, + { + "id" : "minecraft:soul_sand", + "blockRuntimeId" : 8479 + }, + { + "id" : "minecraft:netherrack", + "blockRuntimeId" : 10881 + }, + { + "id" : "minecraft:magma", + "blockRuntimeId" : 12253 + }, + { + "id" : "minecraft:nether_wart" + }, + { + "id" : "minecraft:end_stone", + "blockRuntimeId" : 5679 + }, + { + "id" : "minecraft:chorus_flower", + "blockRuntimeId" : 6377 + }, + { + "id" : "minecraft:chorus_plant", + "blockRuntimeId" : 8152 + }, + { + "id" : "minecraft:chorus_fruit" + }, + { + "id" : "minecraft:popped_chorus_fruit" + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 922 + }, + { + "id" : "minecraft:sponge", + "blockRuntimeId" : 923 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7868 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7869 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7870 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7871 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7872 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7873 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7874 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7875 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7876 + }, + { + "id" : "minecraft:coral_block", + "blockRuntimeId" : 7877 + }, + { + "id" : "minecraft:sculk", + "blockRuntimeId" : 10880 + }, + { + "id" : "minecraft:sculk_vein", + "blockRuntimeId" : 11360 + }, + { + "id" : "minecraft:sculk_catalyst", + "blockRuntimeId" : 5072 + }, + { + "id" : "minecraft:sculk_shrieker", + "blockRuntimeId" : 222 + }, + { + "id" : "minecraft:sculk_sensor", + "blockRuntimeId" : 6236 + }, + { + "id" : "minecraft:reinforced_deepslate", + "blockRuntimeId" : 8864 + }, + { + "id" : "minecraft:leather_helmet" + }, + { + "id" : "minecraft:chainmail_helmet" + }, + { + "id" : "minecraft:iron_helmet" + }, + { + "id" : "minecraft:golden_helmet" + }, + { + "id" : "minecraft:diamond_helmet" + }, + { + "id" : "minecraft:netherite_helmet" + }, + { + "id" : "minecraft:leather_chestplate" + }, + { + "id" : "minecraft:chainmail_chestplate" + }, + { + "id" : "minecraft:iron_chestplate" + }, + { + "id" : "minecraft:golden_chestplate" + }, + { + "id" : "minecraft:diamond_chestplate" + }, + { + "id" : "minecraft:netherite_chestplate" + }, + { + "id" : "minecraft:leather_leggings" + }, + { + "id" : "minecraft:chainmail_leggings" + }, + { + "id" : "minecraft:iron_leggings" + }, + { + "id" : "minecraft:golden_leggings" + }, + { + "id" : "minecraft:diamond_leggings" + }, + { + "id" : "minecraft:netherite_leggings" + }, + { + "id" : "minecraft:leather_boots" + }, + { + "id" : "minecraft:chainmail_boots" + }, + { + "id" : "minecraft:iron_boots" + }, + { + "id" : "minecraft:golden_boots" + }, + { + "id" : "minecraft:diamond_boots" + }, + { + "id" : "minecraft:netherite_boots" + }, + { + "id" : "minecraft:wooden_sword" + }, + { + "id" : "minecraft:stone_sword" + }, + { + "id" : "minecraft:iron_sword" + }, + { + "id" : "minecraft:golden_sword" + }, + { + "id" : "minecraft:diamond_sword" + }, + { + "id" : "minecraft:netherite_sword" + }, + { + "id" : "minecraft:wooden_axe" + }, + { + "id" : "minecraft:stone_axe" + }, + { + "id" : "minecraft:iron_axe" + }, + { + "id" : "minecraft:golden_axe" + }, + { + "id" : "minecraft:diamond_axe" + }, + { + "id" : "minecraft:netherite_axe" + }, + { + "id" : "minecraft:wooden_pickaxe" + }, + { + "id" : "minecraft:stone_pickaxe" + }, + { + "id" : "minecraft:iron_pickaxe" + }, + { + "id" : "minecraft:golden_pickaxe" + }, + { + "id" : "minecraft:diamond_pickaxe" + }, + { + "id" : "minecraft:netherite_pickaxe" + }, + { + "id" : "minecraft:wooden_shovel" + }, + { + "id" : "minecraft:stone_shovel" + }, + { + "id" : "minecraft:iron_shovel" + }, + { + "id" : "minecraft:golden_shovel" + }, + { + "id" : "minecraft:diamond_shovel" + }, + { + "id" : "minecraft:netherite_shovel" + }, + { + "id" : "minecraft:wooden_hoe" + }, + { + "id" : "minecraft:stone_hoe" + }, + { + "id" : "minecraft:iron_hoe" + }, + { + "id" : "minecraft:golden_hoe" + }, + { + "id" : "minecraft:diamond_hoe" + }, + { + "id" : "minecraft:netherite_hoe" + }, + { + "id" : "minecraft:bow" + }, + { + "id" : "minecraft:crossbow" + }, + { + "id" : "minecraft:arrow" + }, + { + "id" : "minecraft:arrow", + "damage" : 6 + }, + { + "id" : "minecraft:arrow", + "damage" : 7 + }, + { + "id" : "minecraft:arrow", + "damage" : 8 + }, + { + "id" : "minecraft:arrow", + "damage" : 9 + }, + { + "id" : "minecraft:arrow", + "damage" : 10 + }, + { + "id" : "minecraft:arrow", + "damage" : 11 + }, + { + "id" : "minecraft:arrow", + "damage" : 12 + }, + { + "id" : "minecraft:arrow", + "damage" : 13 + }, + { + "id" : "minecraft:arrow", + "damage" : 14 + }, + { + "id" : "minecraft:arrow", + "damage" : 15 + }, + { + "id" : "minecraft:arrow", + "damage" : 16 + }, + { + "id" : "minecraft:arrow", + "damage" : 17 + }, + { + "id" : "minecraft:arrow", + "damage" : 18 + }, + { + "id" : "minecraft:arrow", + "damage" : 19 + }, + { + "id" : "minecraft:arrow", + "damage" : 20 + }, + { + "id" : "minecraft:arrow", + "damage" : 21 + }, + { + "id" : "minecraft:arrow", + "damage" : 22 + }, + { + "id" : "minecraft:arrow", + "damage" : 23 + }, + { + "id" : "minecraft:arrow", + "damage" : 24 + }, + { + "id" : "minecraft:arrow", + "damage" : 25 + }, + { + "id" : "minecraft:arrow", + "damage" : 26 + }, + { + "id" : "minecraft:arrow", + "damage" : 27 + }, + { + "id" : "minecraft:arrow", + "damage" : 28 + }, + { + "id" : "minecraft:arrow", + "damage" : 29 + }, + { + "id" : "minecraft:arrow", + "damage" : 30 + }, + { + "id" : "minecraft:arrow", + "damage" : 31 + }, + { + "id" : "minecraft:arrow", + "damage" : 32 + }, + { + "id" : "minecraft:arrow", + "damage" : 33 + }, + { + "id" : "minecraft:arrow", + "damage" : 34 + }, + { + "id" : "minecraft:arrow", + "damage" : 35 + }, + { + "id" : "minecraft:arrow", + "damage" : 36 + }, + { + "id" : "minecraft:arrow", + "damage" : 37 + }, + { + "id" : "minecraft:arrow", + "damage" : 38 + }, + { + "id" : "minecraft:arrow", + "damage" : 39 + }, + { + "id" : "minecraft:arrow", + "damage" : 40 + }, + { + "id" : "minecraft:arrow", + "damage" : 41 + }, + { + "id" : "minecraft:arrow", + "damage" : 42 + }, + { + "id" : "minecraft:arrow", + "damage" : 43 + }, + { + "id" : "minecraft:shield" + }, + { + "id" : "minecraft:cooked_chicken" + }, + { + "id" : "minecraft:cooked_porkchop" + }, + { + "id" : "minecraft:cooked_beef" + }, + { + "id" : "minecraft:cooked_mutton" + }, + { + "id" : "minecraft:cooked_rabbit" + }, + { + "id" : "minecraft:cooked_cod" + }, + { + "id" : "minecraft:cooked_salmon" + }, + { + "id" : "minecraft:bread" + }, + { + "id" : "minecraft:mushroom_stew" + }, + { + "id" : "minecraft:beetroot_soup" + }, + { + "id" : "minecraft:rabbit_stew" + }, + { + "id" : "minecraft:baked_potato" + }, + { + "id" : "minecraft:cookie" + }, + { + "id" : "minecraft:pumpkin_pie" + }, + { + "id" : "minecraft:cake" + }, + { + "id" : "minecraft:dried_kelp" + }, + { + "id" : "minecraft:fishing_rod" + }, + { + "id" : "minecraft:carrot_on_a_stick" + }, + { + "id" : "minecraft:warped_fungus_on_a_stick" + }, + { + "id" : "minecraft:snowball" + }, + { + "id" : "minecraft:shears" + }, + { + "id" : "minecraft:flint_and_steel" + }, + { + "id" : "minecraft:lead" + }, + { + "id" : "minecraft:clock" + }, + { + "id" : "minecraft:compass" + }, + { + "id" : "minecraft:recovery_compass" + }, + { + "id" : "minecraft:goat_horn" + }, + { + "id" : "minecraft:goat_horn", + "damage" : 1 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 2 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 3 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 4 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 5 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 6 + }, + { + "id" : "minecraft:goat_horn", + "damage" : 7 + }, + { + "id" : "minecraft:empty_map" + }, + { + "id" : "minecraft:empty_map", + "damage" : 2 + }, + { + "id" : "minecraft:saddle" + }, + { + "id" : "minecraft:leather_horse_armor" + }, + { + "id" : "minecraft:iron_horse_armor" + }, + { + "id" : "minecraft:golden_horse_armor" + }, + { + "id" : "minecraft:diamond_horse_armor" + }, + { + "id" : "minecraft:trident" + }, + { + "id" : "minecraft:turtle_helmet" + }, + { + "id" : "minecraft:elytra" + }, + { + "id" : "minecraft:totem_of_undying" + }, + { + "id" : "minecraft:glass_bottle" + }, + { + "id" : "minecraft:experience_bottle" + }, + { + "id" : "minecraft:potion" + }, + { + "id" : "minecraft:potion", + "damage" : 1 + }, + { + "id" : "minecraft:potion", + "damage" : 2 + }, + { + "id" : "minecraft:potion", + "damage" : 3 + }, + { + "id" : "minecraft:potion", + "damage" : 4 + }, + { + "id" : "minecraft:potion", + "damage" : 5 + }, + { + "id" : "minecraft:potion", + "damage" : 6 + }, + { + "id" : "minecraft:potion", + "damage" : 7 + }, + { + "id" : "minecraft:potion", + "damage" : 8 + }, + { + "id" : "minecraft:potion", + "damage" : 9 + }, + { + "id" : "minecraft:potion", + "damage" : 10 + }, + { + "id" : "minecraft:potion", + "damage" : 11 + }, + { + "id" : "minecraft:potion", + "damage" : 12 + }, + { + "id" : "minecraft:potion", + "damage" : 13 + }, + { + "id" : "minecraft:potion", + "damage" : 14 + }, + { + "id" : "minecraft:potion", + "damage" : 15 + }, + { + "id" : "minecraft:potion", + "damage" : 16 + }, + { + "id" : "minecraft:potion", + "damage" : 17 + }, + { + "id" : "minecraft:potion", + "damage" : 18 + }, + { + "id" : "minecraft:potion", + "damage" : 19 + }, + { + "id" : "minecraft:potion", + "damage" : 20 + }, + { + "id" : "minecraft:potion", + "damage" : 21 + }, + { + "id" : "minecraft:potion", + "damage" : 22 + }, + { + "id" : "minecraft:potion", + "damage" : 23 + }, + { + "id" : "minecraft:potion", + "damage" : 24 + }, + { + "id" : "minecraft:potion", + "damage" : 25 + }, + { + "id" : "minecraft:potion", + "damage" : 26 + }, + { + "id" : "minecraft:potion", + "damage" : 27 + }, + { + "id" : "minecraft:potion", + "damage" : 28 + }, + { + "id" : "minecraft:potion", + "damage" : 29 + }, + { + "id" : "minecraft:potion", + "damage" : 30 + }, + { + "id" : "minecraft:potion", + "damage" : 31 + }, + { + "id" : "minecraft:potion", + "damage" : 32 + }, + { + "id" : "minecraft:potion", + "damage" : 33 + }, + { + "id" : "minecraft:potion", + "damage" : 34 + }, + { + "id" : "minecraft:potion", + "damage" : 35 + }, + { + "id" : "minecraft:potion", + "damage" : 36 + }, + { + "id" : "minecraft:potion", + "damage" : 37 + }, + { + "id" : "minecraft:potion", + "damage" : 38 + }, + { + "id" : "minecraft:potion", + "damage" : 39 + }, + { + "id" : "minecraft:potion", + "damage" : 40 + }, + { + "id" : "minecraft:potion", + "damage" : 41 + }, + { + "id" : "minecraft:potion", + "damage" : 42 + }, + { + "id" : "minecraft:splash_potion" + }, + { + "id" : "minecraft:splash_potion", + "damage" : 1 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 2 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 3 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 4 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 5 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 6 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 7 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 8 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 9 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 10 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 11 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 12 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 13 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 14 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 15 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 16 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 17 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 18 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 19 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 20 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 21 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 22 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 23 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 24 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 25 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 26 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 27 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 28 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 29 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 30 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 31 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 32 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 33 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 34 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 35 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 36 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 37 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 38 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 39 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 40 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 41 + }, + { + "id" : "minecraft:splash_potion", + "damage" : 42 + }, + { + "id" : "minecraft:lingering_potion" + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 1 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 2 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 3 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 4 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 5 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 6 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 7 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 8 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 9 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 10 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 11 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 12 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 13 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 14 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 15 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 16 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 17 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 18 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 19 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 20 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 21 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 22 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 23 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 24 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 25 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 26 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 27 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 28 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 29 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 30 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 31 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 32 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 33 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 34 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 35 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 36 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 37 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 38 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 39 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 40 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 41 + }, + { + "id" : "minecraft:lingering_potion", + "damage" : 42 + }, + { + "id" : "minecraft:spyglass" + }, + { + "id" : "minecraft:stick" + }, + { + "id" : "minecraft:bed" + }, + { + "id" : "minecraft:bed", + "damage" : 8 + }, + { + "id" : "minecraft:bed", + "damage" : 7 + }, + { + "id" : "minecraft:bed", + "damage" : 15 + }, + { + "id" : "minecraft:bed", + "damage" : 12 + }, + { + "id" : "minecraft:bed", + "damage" : 14 + }, + { + "id" : "minecraft:bed", + "damage" : 1 + }, + { + "id" : "minecraft:bed", + "damage" : 4 + }, + { + "id" : "minecraft:bed", + "damage" : 5 + }, + { + "id" : "minecraft:bed", + "damage" : 13 + }, + { + "id" : "minecraft:bed", + "damage" : 9 + }, + { + "id" : "minecraft:bed", + "damage" : 3 + }, + { + "id" : "minecraft:bed", + "damage" : 11 + }, + { + "id" : "minecraft:bed", + "damage" : 10 + }, + { + "id" : "minecraft:bed", + "damage" : 2 + }, + { + "id" : "minecraft:bed", + "damage" : 6 + }, + { + "id" : "minecraft:torch", + "blockRuntimeId" : 1410 + }, + { + "id" : "minecraft:soul_torch", + "blockRuntimeId" : 6875 + }, + { + "id" : "minecraft:sea_pickle", + "blockRuntimeId" : 8887 + }, + { + "id" : "minecraft:lantern", + "blockRuntimeId" : 10918 + }, + { + "id" : "minecraft:soul_lantern", + "blockRuntimeId" : 8397 + }, + { + "id" : "minecraft:candle", + "blockRuntimeId" : 11647 + }, + { + "id" : "minecraft:white_candle", + "blockRuntimeId" : 7947 + }, + { + "id" : "minecraft:orange_candle", + "blockRuntimeId" : 655 + }, + { + "id" : "minecraft:magenta_candle", + "blockRuntimeId" : 711 + }, + { + "id" : "minecraft:light_blue_candle", + "blockRuntimeId" : 6416 + }, + { + "id" : "minecraft:yellow_candle", + "blockRuntimeId" : 10008 + }, + { + "id" : "minecraft:lime_candle", + "blockRuntimeId" : 10192 + }, + { + "id" : "minecraft:pink_candle", + "blockRuntimeId" : 11614 + }, + { + "id" : "minecraft:gray_candle", + "blockRuntimeId" : 1625 + }, + { + "id" : "minecraft:light_gray_candle", + "blockRuntimeId" : 10048 + }, + { + "id" : "minecraft:cyan_candle", + "blockRuntimeId" : 11970 + }, + { + "id" : "minecraft:purple_candle", + "blockRuntimeId" : 10882 + }, + { + "id" : "minecraft:blue_candle" + }, + { + "id" : "minecraft:brown_candle", + "blockRuntimeId" : 9291 + }, + { + "id" : "minecraft:green_candle", + "blockRuntimeId" : 1364 + }, + { + "id" : "minecraft:red_candle", + "blockRuntimeId" : 6912 + }, + { + "id" : "minecraft:black_candle", + "blockRuntimeId" : 174 + }, + { + "id" : "minecraft:crafting_table", + "blockRuntimeId" : 8886 + }, + { + "id" : "minecraft:cartography_table", + "blockRuntimeId" : 12533 + }, + { + "id" : "minecraft:fletching_table", + "blockRuntimeId" : 8865 + }, + { + "id" : "minecraft:smithing_table", + "blockRuntimeId" : 5185 + }, + { + "id" : "minecraft:beehive", + "blockRuntimeId" : 9924 + }, + { + "id" : "minecraft:campfire" + }, + { + "id" : "minecraft:soul_campfire" + }, + { + "id" : "minecraft:furnace", + "blockRuntimeId" : 12046 + }, + { + "id" : "minecraft:blast_furnace", + "blockRuntimeId" : 11811 + }, + { + "id" : "minecraft:smoker", + "blockRuntimeId" : 1325 + }, + { + "id" : "minecraft:respawn_anchor", + "blockRuntimeId" : 1359 + }, + { + "id" : "minecraft:brewing_stand" + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10472 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10476 + }, + { + "id" : "minecraft:anvil", + "blockRuntimeId" : 10480 + }, + { + "id" : "minecraft:grindstone", + "blockRuntimeId" : 12283 + }, + { + "id" : "minecraft:enchanting_table", + "blockRuntimeId" : 10561 + }, + { + "id" : "minecraft:bookshelf", + "blockRuntimeId" : 10509 + }, + { + "id" : "minecraft:lectern", + "blockRuntimeId" : 10784 + }, + { + "id" : "minecraft:cauldron" + }, + { + "id" : "minecraft:composter", + "blockRuntimeId" : 8062 + }, + { + "id" : "minecraft:chest", + "blockRuntimeId" : 10959 + }, + { + "id" : "minecraft:trapped_chest", + "blockRuntimeId" : 8230 + }, + { + "id" : "minecraft:ender_chest", + "blockRuntimeId" : 6216 + }, + { + "id" : "minecraft:barrel", + "blockRuntimeId" : 6365 + }, + { + "id" : "minecraft:undyed_shulker_box", + "blockRuntimeId" : 5140 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7963 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7971 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7970 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7978 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7975 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7977 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7964 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7967 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7968 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7976 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7972 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7966 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7974 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7973 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7965 + }, + { + "id" : "minecraft:shulker_box", + "blockRuntimeId" : 7969 + }, + { + "id" : "minecraft:armor_stand" + }, + { + "id" : "minecraft:noteblock", + "blockRuntimeId" : 639 + }, + { + "id" : "minecraft:jukebox", + "blockRuntimeId" : 7489 + }, + { + "id" : "minecraft:music_disc_13" + }, + { + "id" : "minecraft:music_disc_cat" + }, + { + "id" : "minecraft:music_disc_blocks" + }, + { + "id" : "minecraft:music_disc_chirp" + }, + { + "id" : "minecraft:music_disc_far" + }, + { + "id" : "minecraft:music_disc_mall" + }, + { + "id" : "minecraft:music_disc_mellohi" + }, + { + "id" : "minecraft:music_disc_stal" + }, + { + "id" : "minecraft:music_disc_strad" + }, + { + "id" : "minecraft:music_disc_ward" + }, + { + "id" : "minecraft:music_disc_11" + }, + { + "id" : "minecraft:music_disc_wait" + }, + { + "id" : "minecraft:music_disc_otherside" + }, + { + "id" : "minecraft:music_disc_5" + }, + { + "id" : "minecraft:music_disc_pigstep" + }, + { + "id" : "minecraft:disc_fragment_5" + }, + { + "id" : "minecraft:glowstone_dust" + }, + { + "id" : "minecraft:glowstone", + "blockRuntimeId" : 5728 + }, + { + "id" : "minecraft:redstone_lamp", + "blockRuntimeId" : 254 + }, + { + "id" : "minecraft:sea_lantern", + "blockRuntimeId" : 11790 + }, + { + "id" : "minecraft:oak_sign" + }, + { + "id" : "minecraft:spruce_sign" + }, + { + "id" : "minecraft:birch_sign" + }, + { + "id" : "minecraft:jungle_sign" + }, + { + "id" : "minecraft:acacia_sign" + }, + { + "id" : "minecraft:dark_oak_sign" + }, + { + "id" : "minecraft:mangrove_sign" + }, + { + "id" : "minecraft:crimson_sign" + }, + { + "id" : "minecraft:warped_sign" + }, + { + "id" : "minecraft:painting" + }, + { + "id" : "minecraft:frame" + }, + { + "id" : "minecraft:glow_frame" + }, + { + "id" : "minecraft:honey_bottle" + }, + { + "id" : "minecraft:flower_pot" + }, + { + "id" : "minecraft:bowl" + }, + { + "id" : "minecraft:bucket" + }, + { + "id" : "minecraft:milk_bucket" + }, + { + "id" : "minecraft:water_bucket" + }, + { + "id" : "minecraft:lava_bucket" + }, + { + "id" : "minecraft:cod_bucket" + }, + { + "id" : "minecraft:salmon_bucket" + }, + { + "id" : "minecraft:tropical_fish_bucket" + }, + { + "id" : "minecraft:pufferfish_bucket" + }, + { + "id" : "minecraft:powder_snow_bucket" + }, + { + "id" : "minecraft:axolotl_bucket" + }, + { + "id" : "minecraft:tadpole_bucket" + }, + { + "id" : "minecraft:skull", + "damage" : 3 + }, + { + "id" : "minecraft:skull", + "damage" : 2 + }, + { + "id" : "minecraft:skull", + "damage" : 4 + }, + { + "id" : "minecraft:skull", + "damage" : 5 + }, + { + "id" : "minecraft:skull" + }, + { + "id" : "minecraft:skull", + "damage" : 1 + }, + { + "id" : "minecraft:beacon", + "blockRuntimeId" : 148 + }, + { + "id" : "minecraft:bell", + "blockRuntimeId" : 10752 + }, + { + "id" : "minecraft:conduit", + "blockRuntimeId" : 6077 + }, + { + "id" : "minecraft:stonecutter_block", + "blockRuntimeId" : 11818 + }, + { + "id" : "minecraft:end_portal_frame", + "blockRuntimeId" : 9877 + }, + { + "id" : "minecraft:coal" + }, + { + "id" : "minecraft:charcoal" + }, + { + "id" : "minecraft:diamond" + }, + { + "id" : "minecraft:iron_nugget" + }, + { + "id" : "minecraft:raw_iron" + }, + { + "id" : "minecraft:raw_gold" + }, + { + "id" : "minecraft:raw_copper" + }, + { + "id" : "minecraft:copper_ingot" + }, + { + "id" : "minecraft:iron_ingot" + }, + { + "id" : "minecraft:netherite_scrap" + }, + { + "id" : "minecraft:netherite_ingot" + }, + { + "id" : "minecraft:gold_nugget" + }, + { + "id" : "minecraft:gold_ingot" + }, + { + "id" : "minecraft:emerald" + }, + { + "id" : "minecraft:quartz" + }, + { + "id" : "minecraft:clay_ball" + }, + { + "id" : "minecraft:brick" + }, + { + "id" : "minecraft:netherbrick" + }, + { + "id" : "minecraft:prismarine_shard" + }, + { + "id" : "minecraft:amethyst_shard" + }, + { + "id" : "minecraft:prismarine_crystals" + }, + { + "id" : "minecraft:nautilus_shell" + }, + { + "id" : "minecraft:heart_of_the_sea" + }, + { + "id" : "minecraft:scute" + }, + { + "id" : "minecraft:phantom_membrane" + }, + { + "id" : "minecraft:string" + }, + { + "id" : "minecraft:feather" + }, + { + "id" : "minecraft:flint" + }, + { + "id" : "minecraft:gunpowder" + }, + { + "id" : "minecraft:leather" + }, + { + "id" : "minecraft:rabbit_hide" + }, + { + "id" : "minecraft:rabbit_foot" + }, + { + "id" : "minecraft:fire_charge" + }, + { + "id" : "minecraft:blaze_rod" + }, + { + "id" : "minecraft:blaze_powder" + }, + { + "id" : "minecraft:magma_cream" + }, + { + "id" : "minecraft:fermented_spider_eye" + }, + { + "id" : "minecraft:echo_shard" + }, + { + "id" : "minecraft:dragon_breath" + }, + { + "id" : "minecraft:shulker_shell" + }, + { + "id" : "minecraft:ghast_tear" + }, + { + "id" : "minecraft:slime_ball" + }, + { + "id" : "minecraft:ender_pearl" + }, + { + "id" : "minecraft:ender_eye" + }, + { + "id" : "minecraft:nether_star" + }, + { + "id" : "minecraft:end_rod", + "blockRuntimeId" : 9307 + }, + { + "id" : "minecraft:lightning_rod", + "blockRuntimeId" : 1862 + }, + { + "id" : "minecraft:end_crystal" + }, + { + "id" : "minecraft:paper" + }, + { + "id" : "minecraft:book" + }, + { + "id" : "minecraft:writable_book" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA=" + }, + { + "id" : "minecraft:enchanted_book", + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA=" + }, + { + "id" : "minecraft:oak_boat" + }, + { + "id" : "minecraft:spruce_boat" + }, + { + "id" : "minecraft:birch_boat" + }, + { + "id" : "minecraft:jungle_boat" + }, + { + "id" : "minecraft:acacia_boat" + }, + { + "id" : "minecraft:dark_oak_boat" + }, + { + "id" : "minecraft:mangrove_boat" + }, + { + "id" : "minecraft:oak_chest_boat" + }, + { + "id" : "minecraft:spruce_chest_boat" + }, + { + "id" : "minecraft:birch_chest_boat" + }, + { + "id" : "minecraft:jungle_chest_boat" + }, + { + "id" : "minecraft:acacia_chest_boat" + }, + { + "id" : "minecraft:dark_oak_chest_boat" + }, + { + "id" : "minecraft:mangrove_chest_boat" + }, + { + "id" : "minecraft:rail", + "blockRuntimeId" : 5763 + }, + { + "id" : "minecraft:golden_rail", + "blockRuntimeId" : 7979 + }, + { + "id" : "minecraft:detector_rail", + "blockRuntimeId" : 5975 + }, + { + "id" : "minecraft:activator_rail", + "blockRuntimeId" : 600 + }, + { + "id" : "minecraft:minecart" + }, + { + "id" : "minecraft:chest_minecart" + }, + { + "id" : "minecraft:hopper_minecart" + }, + { + "id" : "minecraft:tnt_minecart" + }, + { + "id" : "minecraft:redstone" + }, + { + "id" : "minecraft:redstone_block", + "blockRuntimeId" : 5235 + }, + { + "id" : "minecraft:redstone_torch", + "blockRuntimeId" : 4597 + }, + { + "id" : "minecraft:lever", + "blockRuntimeId" : 10350 + }, + { + "id" : "minecraft:wooden_button", + "blockRuntimeId" : 10215 + }, + { + "id" : "minecraft:spruce_button", + "blockRuntimeId" : 6168 + }, + { + "id" : "minecraft:birch_button", + "blockRuntimeId" : 12010 + }, + { + "id" : "minecraft:jungle_button", + "blockRuntimeId" : 119 + }, + { + "id" : "minecraft:acacia_button", + "blockRuntimeId" : 11475 + }, + { + "id" : "minecraft:dark_oak_button", + "blockRuntimeId" : 96 + }, + { + "id" : "minecraft:mangrove_button", + "blockRuntimeId" : 10906 + }, + { + "id" : "minecraft:stone_button", + "blockRuntimeId" : 889 + }, + { + "id" : "minecraft:crimson_button", + "blockRuntimeId" : 6279 + }, + { + "id" : "minecraft:warped_button", + "blockRuntimeId" : 11494 + }, + { + "id" : "minecraft:polished_blackstone_button", + "blockRuntimeId" : 12034 + }, + { + "id" : "minecraft:tripwire_hook", + "blockRuntimeId" : 9330 + }, + { + "id" : "minecraft:wooden_pressure_plate", + "blockRuntimeId" : 12307 + }, + { + "id" : "minecraft:spruce_pressure_plate", + "blockRuntimeId" : 5218 + }, + { + "id" : "minecraft:birch_pressure_plate", + "blockRuntimeId" : 5011 + }, + { + "id" : "minecraft:jungle_pressure_plate", + "blockRuntimeId" : 5094 + }, + { + "id" : "minecraft:acacia_pressure_plate", + "blockRuntimeId" : 7878 + }, + { + "id" : "minecraft:dark_oak_pressure_plate", + "blockRuntimeId" : 9372 + }, + { + "id" : "minecraft:mangrove_pressure_plate", + "blockRuntimeId" : 5712 + }, + { + "id" : "minecraft:crimson_pressure_plate", + "blockRuntimeId" : 12513 + }, + { + "id" : "minecraft:warped_pressure_plate", + "blockRuntimeId" : 259 + }, + { + "id" : "minecraft:stone_pressure_plate", + "blockRuntimeId" : 5729 + }, + { + "id" : "minecraft:light_weighted_pressure_plate", + "blockRuntimeId" : 5124 + }, + { + "id" : "minecraft:heavy_weighted_pressure_plate", + "blockRuntimeId" : 1846 + }, + { + "id" : "minecraft:polished_blackstone_pressure_plate", + "blockRuntimeId" : 10056 + }, + { + "id" : "minecraft:observer", + "blockRuntimeId" : 4585 + }, + { + "id" : "minecraft:daylight_detector", + "blockRuntimeId" : 6042 + }, + { + "id" : "minecraft:repeater" + }, + { + "id" : "minecraft:comparator" + }, + { + "id" : "minecraft:hopper" + }, + { + "id" : "minecraft:dropper", + "blockRuntimeId" : 11629 + }, + { + "id" : "minecraft:dispenser", + "blockRuntimeId" : 12257 + }, + { + "id" : "minecraft:piston", + "blockRuntimeId" : 1608 + }, + { + "id" : "minecraft:sticky_piston", + "blockRuntimeId" : 6211 + }, + { + "id" : "minecraft:tnt", + "blockRuntimeId" : 10545 + }, + { + "id" : "minecraft:name_tag" + }, + { + "id" : "minecraft:loom", + "blockRuntimeId" : 5669 + }, + { + "id" : "minecraft:banner" + }, + { + "id" : "minecraft:banner", + "damage" : 8 + }, + { + "id" : "minecraft:banner", + "damage" : 7 + }, + { + "id" : "minecraft:banner", + "damage" : 15 + }, + { + "id" : "minecraft:banner", + "damage" : 12 + }, + { + "id" : "minecraft:banner", + "damage" : 14 + }, + { + "id" : "minecraft:banner", + "damage" : 1 + }, + { + "id" : "minecraft:banner", + "damage" : 4 + }, + { + "id" : "minecraft:banner", + "damage" : 5 + }, + { + "id" : "minecraft:banner", + "damage" : 13 + }, + { + "id" : "minecraft:banner", + "damage" : 9 + }, + { + "id" : "minecraft:banner", + "damage" : 3 + }, + { + "id" : "minecraft:banner", + "damage" : 11 + }, + { + "id" : "minecraft:banner", + "damage" : 10 + }, + { + "id" : "minecraft:banner", + "damage" : 2 + }, + { + "id" : "minecraft:banner", + "damage" : 6 + }, + { + "id" : "minecraft:banner", + "damage" : 15, + "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" + }, + { + "id" : "minecraft:creeper_banner_pattern" + }, + { + "id" : "minecraft:skull_banner_pattern" + }, + { + "id" : "minecraft:flower_banner_pattern" + }, + { + "id" : "minecraft:mojang_banner_pattern" + }, + { + "id" : "minecraft:field_masoned_banner_pattern" + }, + { + "id" : "minecraft:bordure_indented_banner_pattern" + }, + { + "id" : "minecraft:piglin_banner_pattern" + }, + { + "id" : "minecraft:globe_banner_pattern" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_rocket", + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" + }, + { + "id" : "minecraft:firework_star", + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 8, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 7, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 15, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 12, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 14, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 1, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 4, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 5, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 13, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 9, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 3, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 11, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 10, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 2, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" + }, + { + "id" : "minecraft:firework_star", + "damage" : 6, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : "minecraft:chain" + }, + { + "id" : "minecraft:target", + "blockRuntimeId" : 10214 + }, + { + "id" : "minecraft:lodestone_compass" + }, + { + "id" : "minecraft:wither_spawn_egg" + }, + { + "id" : "minecraft:ender_dragon_spawn_egg" + } + ] +} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/runtime_item_states.1_19_60.json b/core/src/main/resources/bedrock/runtime_item_states.1_19_60.json new file mode 100644 index 000000000..cdc661947 --- /dev/null +++ b/core/src/main/resources/bedrock/runtime_item_states.1_19_60.json @@ -0,0 +1,4682 @@ +[ + { + "name" : "minecraft:acacia_boat", + "id" : 379 + }, + { + "name" : "minecraft:acacia_button", + "id" : -140 + }, + { + "name" : "minecraft:acacia_chest_boat", + "id" : 647 + }, + { + "name" : "minecraft:acacia_door", + "id" : 561 + }, + { + "name" : "minecraft:acacia_fence_gate", + "id" : 187 + }, + { + "name" : "minecraft:acacia_hanging_sign", + "id" : -504 + }, + { + "name" : "minecraft:acacia_pressure_plate", + "id" : -150 + }, + { + "name" : "minecraft:acacia_sign", + "id" : 584 + }, + { + "name" : "minecraft:acacia_stairs", + "id" : 163 + }, + { + "name" : "minecraft:acacia_standing_sign", + "id" : -190 + }, + { + "name" : "minecraft:acacia_trapdoor", + "id" : -145 + }, + { + "name" : "minecraft:acacia_wall_sign", + "id" : -191 + }, + { + "name" : "minecraft:activator_rail", + "id" : 126 + }, + { + "name" : "minecraft:agent_spawn_egg", + "id" : 487 + }, + { + "name" : "minecraft:air", + "id" : -158 + }, + { + "name" : "minecraft:allay_spawn_egg", + "id" : 636 + }, + { + "name" : "minecraft:allow", + "id" : 210 + }, + { + "name" : "minecraft:amethyst_block", + "id" : -327 + }, + { + "name" : "minecraft:amethyst_cluster", + "id" : -329 + }, + { + "name" : "minecraft:amethyst_shard", + "id" : 629 + }, + { + "name" : "minecraft:ancient_debris", + "id" : -271 + }, + { + "name" : "minecraft:andesite_stairs", + "id" : -171 + }, + { + "name" : "minecraft:anvil", + "id" : 145 + }, + { + "name" : "minecraft:apple", + "id" : 257 + }, + { + "name" : "minecraft:armor_stand", + "id" : 557 + }, + { + "name" : "minecraft:arrow", + "id" : 301 + }, + { + "name" : "minecraft:axolotl_bucket", + "id" : 369 + }, + { + "name" : "minecraft:axolotl_spawn_egg", + "id" : 501 + }, + { + "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" : 603 + }, + { + "name" : "minecraft:bamboo", + "id" : -163 + }, + { + "name" : "minecraft:bamboo_block", + "id" : -527 + }, + { + "name" : "minecraft:bamboo_button", + "id" : -511 + }, + { + "name" : "minecraft:bamboo_chest_raft", + "id" : 656 + }, + { + "name" : "minecraft:bamboo_door", + "id" : -517 + }, + { + "name" : "minecraft:bamboo_double_slab", + "id" : -521 + }, + { + "name" : "minecraft:bamboo_fence", + "id" : -515 + }, + { + "name" : "minecraft:bamboo_fence_gate", + "id" : -516 + }, + { + "name" : "minecraft:bamboo_hanging_sign", + "id" : -522 + }, + { + "name" : "minecraft:bamboo_mosaic", + "id" : -509 + }, + { + "name" : "minecraft:bamboo_mosaic_double_slab", + "id" : -525 + }, + { + "name" : "minecraft:bamboo_mosaic_slab", + "id" : -524 + }, + { + "name" : "minecraft:bamboo_mosaic_stairs", + "id" : -523 + }, + { + "name" : "minecraft:bamboo_planks", + "id" : -510 + }, + { + "name" : "minecraft:bamboo_pressure_plate", + "id" : -514 + }, + { + "name" : "minecraft:bamboo_raft", + "id" : 655 + }, + { + "name" : "minecraft:bamboo_sapling", + "id" : -164 + }, + { + "name" : "minecraft:bamboo_sign", + "id" : 654 + }, + { + "name" : "minecraft:bamboo_slab", + "id" : -513 + }, + { + "name" : "minecraft:bamboo_stairs", + "id" : -512 + }, + { + "name" : "minecraft:bamboo_standing_sign", + "id" : -518 + }, + { + "name" : "minecraft:bamboo_trapdoor", + "id" : -520 + }, + { + "name" : "minecraft:bamboo_wall_sign", + "id" : -519 + }, + { + "name" : "minecraft:banner", + "id" : 572 + }, + { + "name" : "minecraft:banner_pattern", + "id" : 660 + }, + { + "name" : "minecraft:barrel", + "id" : -203 + }, + { + "name" : "minecraft:barrier", + "id" : -161 + }, + { + "name" : "minecraft:basalt", + "id" : -234 + }, + { + "name" : "minecraft:bat_spawn_egg", + "id" : 453 + }, + { + "name" : "minecraft:beacon", + "id" : 138 + }, + { + "name" : "minecraft:bed", + "id" : 418 + }, + { + "name" : "minecraft:bedrock", + "id" : 7 + }, + { + "name" : "minecraft:bee_nest", + "id" : -218 + }, + { + "name" : "minecraft:bee_spawn_egg", + "id" : 494 + }, + { + "name" : "minecraft:beef", + "id" : 273 + }, + { + "name" : "minecraft:beehive", + "id" : -219 + }, + { + "name" : "minecraft:beetroot", + "id" : 285 + }, + { + "name" : "minecraft:beetroot_seeds", + "id" : 295 + }, + { + "name" : "minecraft:beetroot_soup", + "id" : 286 + }, + { + "name" : "minecraft:bell", + "id" : -206 + }, + { + "name" : "minecraft:big_dripleaf", + "id" : -323 + }, + { + "name" : "minecraft:birch_boat", + "id" : 376 + }, + { + "name" : "minecraft:birch_button", + "id" : -141 + }, + { + "name" : "minecraft:birch_chest_boat", + "id" : 644 + }, + { + "name" : "minecraft:birch_door", + "id" : 559 + }, + { + "name" : "minecraft:birch_fence_gate", + "id" : 184 + }, + { + "name" : "minecraft:birch_hanging_sign", + "id" : -502 + }, + { + "name" : "minecraft:birch_pressure_plate", + "id" : -151 + }, + { + "name" : "minecraft:birch_sign", + "id" : 582 + }, + { + "name" : "minecraft:birch_stairs", + "id" : 135 + }, + { + "name" : "minecraft:birch_standing_sign", + "id" : -186 + }, + { + "name" : "minecraft:birch_trapdoor", + "id" : -146 + }, + { + "name" : "minecraft:birch_wall_sign", + "id" : -187 + }, + { + "name" : "minecraft:black_candle", + "id" : -428 + }, + { + "name" : "minecraft:black_candle_cake", + "id" : -445 + }, + { + "name" : "minecraft:black_dye", + "id" : 395 + }, + { + "name" : "minecraft:black_glazed_terracotta", + "id" : 235 + }, + { + "name" : "minecraft:blackstone", + "id" : -273 + }, + { + "name" : "minecraft:blackstone_double_slab", + "id" : -283 + }, + { + "name" : "minecraft:blackstone_slab", + "id" : -282 + }, + { + "name" : "minecraft:blackstone_stairs", + "id" : -276 + }, + { + "name" : "minecraft:blackstone_wall", + "id" : -277 + }, + { + "name" : "minecraft:blast_furnace", + "id" : -196 + }, + { + "name" : "minecraft:blaze_powder", + "id" : 429 + }, + { + "name" : "minecraft:blaze_rod", + "id" : 423 + }, + { + "name" : "minecraft:blaze_spawn_egg", + "id" : 456 + }, + { + "name" : "minecraft:bleach", + "id" : 601 + }, + { + "name" : "minecraft:blue_candle", + "id" : -424 + }, + { + "name" : "minecraft:blue_candle_cake", + "id" : -441 + }, + { + "name" : "minecraft:blue_dye", + "id" : 399 + }, + { + "name" : "minecraft:blue_glazed_terracotta", + "id" : 231 + }, + { + "name" : "minecraft:blue_ice", + "id" : -11 + }, + { + "name" : "minecraft:boat", + "id" : 658 + }, + { + "name" : "minecraft:bone", + "id" : 415 + }, + { + "name" : "minecraft:bone_block", + "id" : 216 + }, + { + "name" : "minecraft:bone_meal", + "id" : 411 + }, + { + "name" : "minecraft:book", + "id" : 387 + }, + { + "name" : "minecraft:bookshelf", + "id" : 47 + }, + { + "name" : "minecraft:border_block", + "id" : 212 + }, + { + "name" : "minecraft:bordure_indented_banner_pattern", + "id" : 591 + }, + { + "name" : "minecraft:bow", + "id" : 300 + }, + { + "name" : "minecraft:bowl", + "id" : 321 + }, + { + "name" : "minecraft:bread", + "id" : 261 + }, + { + "name" : "minecraft:brewing_stand", + "id" : 431 + }, + { + "name" : "minecraft:brick", + "id" : 383 + }, + { + "name" : "minecraft:brick_block", + "id" : 45 + }, + { + "name" : "minecraft:brick_stairs", + "id" : 108 + }, + { + "name" : "minecraft:brown_candle", + "id" : -425 + }, + { + "name" : "minecraft:brown_candle_cake", + "id" : -442 + }, + { + "name" : "minecraft:brown_dye", + "id" : 398 + }, + { + "name" : "minecraft:brown_glazed_terracotta", + "id" : 232 + }, + { + "name" : "minecraft:brown_mushroom", + "id" : 39 + }, + { + "name" : "minecraft:brown_mushroom_block", + "id" : 99 + }, + { + "name" : "minecraft:bubble_column", + "id" : -160 + }, + { + "name" : "minecraft:bucket", + "id" : 360 + }, + { + "name" : "minecraft:budding_amethyst", + "id" : -328 + }, + { + "name" : "minecraft:cactus", + "id" : 81 + }, + { + "name" : "minecraft:cake", + "id" : 417 + }, + { + "name" : "minecraft:calcite", + "id" : -326 + }, + { + "name" : "minecraft:camel_spawn_egg", + "id" : 657 + }, + { + "name" : "minecraft:camera", + "id" : 598 + }, + { + "name" : "minecraft:campfire", + "id" : 594 + }, + { + "name" : "minecraft:candle", + "id" : -412 + }, + { + "name" : "minecraft:candle_cake", + "id" : -429 + }, + { + "name" : "minecraft:carpet", + "id" : 171 + }, + { + "name" : "minecraft:carrot", + "id" : 279 + }, + { + "name" : "minecraft:carrot_on_a_stick", + "id" : 522 + }, + { + "name" : "minecraft:carrots", + "id" : 141 + }, + { + "name" : "minecraft:cartography_table", + "id" : -200 + }, + { + "name" : "minecraft:carved_pumpkin", + "id" : -155 + }, + { + "name" : "minecraft:cat_spawn_egg", + "id" : 488 + }, + { + "name" : "minecraft:cauldron", + "id" : 432 + }, + { + "name" : "minecraft:cave_spider_spawn_egg", + "id" : 457 + }, + { + "name" : "minecraft:cave_vines", + "id" : -322 + }, + { + "name" : "minecraft:cave_vines_body_with_berries", + "id" : -375 + }, + { + "name" : "minecraft:cave_vines_head_with_berries", + "id" : -376 + }, + { + "name" : "minecraft:chain", + "id" : 624 + }, + { + "name" : "minecraft:chain_command_block", + "id" : 189 + }, + { + "name" : "minecraft:chainmail_boots", + "id" : 342 + }, + { + "name" : "minecraft:chainmail_chestplate", + "id" : 340 + }, + { + "name" : "minecraft:chainmail_helmet", + "id" : 339 + }, + { + "name" : "minecraft:chainmail_leggings", + "id" : 341 + }, + { + "name" : "minecraft:charcoal", + "id" : 303 + }, + { + "name" : "minecraft:chemical_heat", + "id" : 192 + }, + { + "name" : "minecraft:chemistry_table", + "id" : 238 + }, + { + "name" : "minecraft:chest", + "id" : 54 + }, + { + "name" : "minecraft:chest_boat", + "id" : 650 + }, + { + "name" : "minecraft:chest_minecart", + "id" : 389 + }, + { + "name" : "minecraft:chicken", + "id" : 275 + }, + { + "name" : "minecraft:chicken_spawn_egg", + "id" : 435 + }, + { + "name" : "minecraft:chiseled_bookshelf", + "id" : -526 + }, + { + "name" : "minecraft:chiseled_deepslate", + "id" : -395 + }, + { + "name" : "minecraft:chiseled_nether_bricks", + "id" : -302 + }, + { + "name" : "minecraft:chiseled_polished_blackstone", + "id" : -279 + }, + { + "name" : "minecraft:chorus_flower", + "id" : 200 + }, + { + "name" : "minecraft:chorus_fruit", + "id" : 563 + }, + { + "name" : "minecraft:chorus_plant", + "id" : 240 + }, + { + "name" : "minecraft:clay", + "id" : 82 + }, + { + "name" : "minecraft:clay_ball", + "id" : 384 + }, + { + "name" : "minecraft:client_request_placeholder_block", + "id" : -465 + }, + { + "name" : "minecraft:clock", + "id" : 393 + }, + { + "name" : "minecraft:coal", + "id" : 302 + }, + { + "name" : "minecraft:coal_block", + "id" : 173 + }, + { + "name" : "minecraft:coal_ore", + "id" : 16 + }, + { + "name" : "minecraft:cobbled_deepslate", + "id" : -379 + }, + { + "name" : "minecraft:cobbled_deepslate_double_slab", + "id" : -396 + }, + { + "name" : "minecraft:cobbled_deepslate_slab", + "id" : -380 + }, + { + "name" : "minecraft:cobbled_deepslate_stairs", + "id" : -381 + }, + { + "name" : "minecraft:cobbled_deepslate_wall", + "id" : -382 + }, + { + "name" : "minecraft:cobblestone", + "id" : 4 + }, + { + "name" : "minecraft:cobblestone_wall", + "id" : 139 + }, + { + "name" : "minecraft:cocoa", + "id" : 127 + }, + { + "name" : "minecraft:cocoa_beans", + "id" : 412 + }, + { + "name" : "minecraft:cod", + "id" : 264 + }, + { + "name" : "minecraft:cod_bucket", + "id" : 364 + }, + { + "name" : "minecraft:cod_spawn_egg", + "id" : 480 + }, + { + "name" : "minecraft:colored_torch_bp", + "id" : 204 + }, + { + "name" : "minecraft:colored_torch_rg", + "id" : 202 + }, + { + "name" : "minecraft:command_block", + "id" : 137 + }, + { + "name" : "minecraft:command_block_minecart", + "id" : 568 + }, + { + "name" : "minecraft:comparator", + "id" : 527 + }, + { + "name" : "minecraft:compass", + "id" : 391 + }, + { + "name" : "minecraft:composter", + "id" : -213 + }, + { + "name" : "minecraft:compound", + "id" : 599 + }, + { + "name" : "minecraft:concrete", + "id" : 236 + }, + { + "name" : "minecraft:concrete_powder", + "id" : 237 + }, + { + "name" : "minecraft:conduit", + "id" : -157 + }, + { + "name" : "minecraft:cooked_beef", + "id" : 274 + }, + { + "name" : "minecraft:cooked_chicken", + "id" : 276 + }, + { + "name" : "minecraft:cooked_cod", + "id" : 268 + }, + { + "name" : "minecraft:cooked_mutton", + "id" : 556 + }, + { + "name" : "minecraft:cooked_porkchop", + "id" : 263 + }, + { + "name" : "minecraft:cooked_rabbit", + "id" : 289 + }, + { + "name" : "minecraft:cooked_salmon", + "id" : 269 + }, + { + "name" : "minecraft:cookie", + "id" : 271 + }, + { + "name" : "minecraft:copper_block", + "id" : -340 + }, + { + "name" : "minecraft:copper_ingot", + "id" : 509 + }, + { + "name" : "minecraft:copper_ore", + "id" : -311 + }, + { + "name" : "minecraft:coral", + "id" : -131 + }, + { + "name" : "minecraft:coral_block", + "id" : -132 + }, + { + "name" : "minecraft:coral_fan", + "id" : -133 + }, + { + "name" : "minecraft:coral_fan_dead", + "id" : -134 + }, + { + "name" : "minecraft:coral_fan_hang", + "id" : -135 + }, + { + "name" : "minecraft:coral_fan_hang2", + "id" : -136 + }, + { + "name" : "minecraft:coral_fan_hang3", + "id" : -137 + }, + { + "name" : "minecraft:cow_spawn_egg", + "id" : 436 + }, + { + "name" : "minecraft:cracked_deepslate_bricks", + "id" : -410 + }, + { + "name" : "minecraft:cracked_deepslate_tiles", + "id" : -409 + }, + { + "name" : "minecraft:cracked_nether_bricks", + "id" : -303 + }, + { + "name" : "minecraft:cracked_polished_blackstone_bricks", + "id" : -280 + }, + { + "name" : "minecraft:crafting_table", + "id" : 58 + }, + { + "name" : "minecraft:creeper_banner_pattern", + "id" : 587 + }, + { + "name" : "minecraft:creeper_spawn_egg", + "id" : 441 + }, + { + "name" : "minecraft:crimson_button", + "id" : -260 + }, + { + "name" : "minecraft:crimson_door", + "id" : 621 + }, + { + "name" : "minecraft:crimson_double_slab", + "id" : -266 + }, + { + "name" : "minecraft:crimson_fence", + "id" : -256 + }, + { + "name" : "minecraft:crimson_fence_gate", + "id" : -258 + }, + { + "name" : "minecraft:crimson_fungus", + "id" : -228 + }, + { + "name" : "minecraft:crimson_hanging_sign", + "id" : -506 + }, + { + "name" : "minecraft:crimson_hyphae", + "id" : -299 + }, + { + "name" : "minecraft:crimson_nylium", + "id" : -232 + }, + { + "name" : "minecraft:crimson_planks", + "id" : -242 + }, + { + "name" : "minecraft:crimson_pressure_plate", + "id" : -262 + }, + { + "name" : "minecraft:crimson_roots", + "id" : -223 + }, + { + "name" : "minecraft:crimson_sign", + "id" : 619 + }, + { + "name" : "minecraft:crimson_slab", + "id" : -264 + }, + { + "name" : "minecraft:crimson_stairs", + "id" : -254 + }, + { + "name" : "minecraft:crimson_standing_sign", + "id" : -250 + }, + { + "name" : "minecraft:crimson_stem", + "id" : -225 + }, + { + "name" : "minecraft:crimson_trapdoor", + "id" : -246 + }, + { + "name" : "minecraft:crimson_wall_sign", + "id" : -252 + }, + { + "name" : "minecraft:crossbow", + "id" : 580 + }, + { + "name" : "minecraft:crying_obsidian", + "id" : -289 + }, + { + "name" : "minecraft:cut_copper", + "id" : -347 + }, + { + "name" : "minecraft:cut_copper_slab", + "id" : -361 + }, + { + "name" : "minecraft:cut_copper_stairs", + "id" : -354 + }, + { + "name" : "minecraft:cyan_candle", + "id" : -422 + }, + { + "name" : "minecraft:cyan_candle_cake", + "id" : -439 + }, + { + "name" : "minecraft:cyan_dye", + "id" : 401 + }, + { + "name" : "minecraft:cyan_glazed_terracotta", + "id" : 229 + }, + { + "name" : "minecraft:dark_oak_boat", + "id" : 380 + }, + { + "name" : "minecraft:dark_oak_button", + "id" : -142 + }, + { + "name" : "minecraft:dark_oak_chest_boat", + "id" : 648 + }, + { + "name" : "minecraft:dark_oak_door", + "id" : 562 + }, + { + "name" : "minecraft:dark_oak_fence_gate", + "id" : 186 + }, + { + "name" : "minecraft:dark_oak_hanging_sign", + "id" : -505 + }, + { + "name" : "minecraft:dark_oak_pressure_plate", + "id" : -152 + }, + { + "name" : "minecraft:dark_oak_sign", + "id" : 585 + }, + { + "name" : "minecraft:dark_oak_stairs", + "id" : 164 + }, + { + "name" : "minecraft:dark_oak_trapdoor", + "id" : -147 + }, + { + "name" : "minecraft:dark_prismarine_stairs", + "id" : -3 + }, + { + "name" : "minecraft:darkoak_standing_sign", + "id" : -192 + }, + { + "name" : "minecraft:darkoak_wall_sign", + "id" : -193 + }, + { + "name" : "minecraft:daylight_detector", + "id" : 151 + }, + { + "name" : "minecraft:daylight_detector_inverted", + "id" : 178 + }, + { + "name" : "minecraft:deadbush", + "id" : 32 + }, + { + "name" : "minecraft:deepslate", + "id" : -378 + }, + { + "name" : "minecraft:deepslate_brick_double_slab", + "id" : -399 + }, + { + "name" : "minecraft:deepslate_brick_slab", + "id" : -392 + }, + { + "name" : "minecraft:deepslate_brick_stairs", + "id" : -393 + }, + { + "name" : "minecraft:deepslate_brick_wall", + "id" : -394 + }, + { + "name" : "minecraft:deepslate_bricks", + "id" : -391 + }, + { + "name" : "minecraft:deepslate_coal_ore", + "id" : -406 + }, + { + "name" : "minecraft:deepslate_copper_ore", + "id" : -408 + }, + { + "name" : "minecraft:deepslate_diamond_ore", + "id" : -405 + }, + { + "name" : "minecraft:deepslate_emerald_ore", + "id" : -407 + }, + { + "name" : "minecraft:deepslate_gold_ore", + "id" : -402 + }, + { + "name" : "minecraft:deepslate_iron_ore", + "id" : -401 + }, + { + "name" : "minecraft:deepslate_lapis_ore", + "id" : -400 + }, + { + "name" : "minecraft:deepslate_redstone_ore", + "id" : -403 + }, + { + "name" : "minecraft:deepslate_tile_double_slab", + "id" : -398 + }, + { + "name" : "minecraft:deepslate_tile_slab", + "id" : -388 + }, + { + "name" : "minecraft:deepslate_tile_stairs", + "id" : -389 + }, + { + "name" : "minecraft:deepslate_tile_wall", + "id" : -390 + }, + { + "name" : "minecraft:deepslate_tiles", + "id" : -387 + }, + { + "name" : "minecraft:deny", + "id" : 211 + }, + { + "name" : "minecraft:detector_rail", + "id" : 28 + }, + { + "name" : "minecraft:diamond", + "id" : 304 + }, + { + "name" : "minecraft:diamond_axe", + "id" : 319 + }, + { + "name" : "minecraft:diamond_block", + "id" : 57 + }, + { + "name" : "minecraft:diamond_boots", + "id" : 350 + }, + { + "name" : "minecraft:diamond_chestplate", + "id" : 348 + }, + { + "name" : "minecraft:diamond_helmet", + "id" : 347 + }, + { + "name" : "minecraft:diamond_hoe", + "id" : 332 + }, + { + "name" : "minecraft:diamond_horse_armor", + "id" : 538 + }, + { + "name" : "minecraft:diamond_leggings", + "id" : 349 + }, + { + "name" : "minecraft:diamond_ore", + "id" : 56 + }, + { + "name" : "minecraft:diamond_pickaxe", + "id" : 318 + }, + { + "name" : "minecraft:diamond_shovel", + "id" : 317 + }, + { + "name" : "minecraft:diamond_sword", + "id" : 316 + }, + { + "name" : "minecraft:diorite_stairs", + "id" : -170 + }, + { + "name" : "minecraft:dirt", + "id" : 3 + }, + { + "name" : "minecraft:dirt_with_roots", + "id" : -318 + }, + { + "name" : "minecraft:disc_fragment_5", + "id" : 642 + }, + { + "name" : "minecraft:dispenser", + "id" : 23 + }, + { + "name" : "minecraft:dolphin_spawn_egg", + "id" : 484 + }, + { + "name" : "minecraft:donkey_spawn_egg", + "id" : 465 + }, + { + "name" : "minecraft:double_cut_copper_slab", + "id" : -368 + }, + { + "name" : "minecraft:double_plant", + "id" : 175 + }, + { + "name" : "minecraft:double_stone_block_slab", + "id" : 43 + }, + { + "name" : "minecraft:double_stone_block_slab2", + "id" : 181 + }, + { + "name" : "minecraft:double_stone_block_slab3", + "id" : -167 + }, + { + "name" : "minecraft:double_stone_block_slab4", + "id" : -168 + }, + { + "name" : "minecraft:double_wooden_slab", + "id" : 157 + }, + { + "name" : "minecraft:dragon_breath", + "id" : 565 + }, + { + "name" : "minecraft:dragon_egg", + "id" : 122 + }, + { + "name" : "minecraft:dried_kelp", + "id" : 270 + }, + { + "name" : "minecraft:dried_kelp_block", + "id" : -139 + }, + { + "name" : "minecraft:dripstone_block", + "id" : -317 + }, + { + "name" : "minecraft:dropper", + "id" : 125 + }, + { + "name" : "minecraft:drowned_spawn_egg", + "id" : 483 + }, + { + "name" : "minecraft:dye", + "id" : 659 + }, + { + "name" : "minecraft:echo_shard", + "id" : 652 + }, + { + "name" : "minecraft:egg", + "id" : 390 + }, + { + "name" : "minecraft:elder_guardian_spawn_egg", + "id" : 471 + }, + { + "name" : "minecraft:element_0", + "id" : 36 + }, + { + "name" : "minecraft:element_1", + "id" : -12 + }, + { + "name" : "minecraft:element_10", + "id" : -21 + }, + { + "name" : "minecraft:element_100", + "id" : -111 + }, + { + "name" : "minecraft:element_101", + "id" : -112 + }, + { + "name" : "minecraft:element_102", + "id" : -113 + }, + { + "name" : "minecraft:element_103", + "id" : -114 + }, + { + "name" : "minecraft:element_104", + "id" : -115 + }, + { + "name" : "minecraft:element_105", + "id" : -116 + }, + { + "name" : "minecraft:element_106", + "id" : -117 + }, + { + "name" : "minecraft:element_107", + "id" : -118 + }, + { + "name" : "minecraft:element_108", + "id" : -119 + }, + { + "name" : "minecraft:element_109", + "id" : -120 + }, + { + "name" : "minecraft:element_11", + "id" : -22 + }, + { + "name" : "minecraft:element_110", + "id" : -121 + }, + { + "name" : "minecraft:element_111", + "id" : -122 + }, + { + "name" : "minecraft:element_112", + "id" : -123 + }, + { + "name" : "minecraft:element_113", + "id" : -124 + }, + { + "name" : "minecraft:element_114", + "id" : -125 + }, + { + "name" : "minecraft:element_115", + "id" : -126 + }, + { + "name" : "minecraft:element_116", + "id" : -127 + }, + { + "name" : "minecraft:element_117", + "id" : -128 + }, + { + "name" : "minecraft:element_118", + "id" : -129 + }, + { + "name" : "minecraft:element_12", + "id" : -23 + }, + { + "name" : "minecraft:element_13", + "id" : -24 + }, + { + "name" : "minecraft:element_14", + "id" : -25 + }, + { + "name" : "minecraft:element_15", + "id" : -26 + }, + { + "name" : "minecraft:element_16", + "id" : -27 + }, + { + "name" : "minecraft:element_17", + "id" : -28 + }, + { + "name" : "minecraft:element_18", + "id" : -29 + }, + { + "name" : "minecraft:element_19", + "id" : -30 + }, + { + "name" : "minecraft:element_2", + "id" : -13 + }, + { + "name" : "minecraft:element_20", + "id" : -31 + }, + { + "name" : "minecraft:element_21", + "id" : -32 + }, + { + "name" : "minecraft:element_22", + "id" : -33 + }, + { + "name" : "minecraft:element_23", + "id" : -34 + }, + { + "name" : "minecraft:element_24", + "id" : -35 + }, + { + "name" : "minecraft:element_25", + "id" : -36 + }, + { + "name" : "minecraft:element_26", + "id" : -37 + }, + { + "name" : "minecraft:element_27", + "id" : -38 + }, + { + "name" : "minecraft:element_28", + "id" : -39 + }, + { + "name" : "minecraft:element_29", + "id" : -40 + }, + { + "name" : "minecraft:element_3", + "id" : -14 + }, + { + "name" : "minecraft:element_30", + "id" : -41 + }, + { + "name" : "minecraft:element_31", + "id" : -42 + }, + { + "name" : "minecraft:element_32", + "id" : -43 + }, + { + "name" : "minecraft:element_33", + "id" : -44 + }, + { + "name" : "minecraft:element_34", + "id" : -45 + }, + { + "name" : "minecraft:element_35", + "id" : -46 + }, + { + "name" : "minecraft:element_36", + "id" : -47 + }, + { + "name" : "minecraft:element_37", + "id" : -48 + }, + { + "name" : "minecraft:element_38", + "id" : -49 + }, + { + "name" : "minecraft:element_39", + "id" : -50 + }, + { + "name" : "minecraft:element_4", + "id" : -15 + }, + { + "name" : "minecraft:element_40", + "id" : -51 + }, + { + "name" : "minecraft:element_41", + "id" : -52 + }, + { + "name" : "minecraft:element_42", + "id" : -53 + }, + { + "name" : "minecraft:element_43", + "id" : -54 + }, + { + "name" : "minecraft:element_44", + "id" : -55 + }, + { + "name" : "minecraft:element_45", + "id" : -56 + }, + { + "name" : "minecraft:element_46", + "id" : -57 + }, + { + "name" : "minecraft:element_47", + "id" : -58 + }, + { + "name" : "minecraft:element_48", + "id" : -59 + }, + { + "name" : "minecraft:element_49", + "id" : -60 + }, + { + "name" : "minecraft:element_5", + "id" : -16 + }, + { + "name" : "minecraft:element_50", + "id" : -61 + }, + { + "name" : "minecraft:element_51", + "id" : -62 + }, + { + "name" : "minecraft:element_52", + "id" : -63 + }, + { + "name" : "minecraft:element_53", + "id" : -64 + }, + { + "name" : "minecraft:element_54", + "id" : -65 + }, + { + "name" : "minecraft:element_55", + "id" : -66 + }, + { + "name" : "minecraft:element_56", + "id" : -67 + }, + { + "name" : "minecraft:element_57", + "id" : -68 + }, + { + "name" : "minecraft:element_58", + "id" : -69 + }, + { + "name" : "minecraft:element_59", + "id" : -70 + }, + { + "name" : "minecraft:element_6", + "id" : -17 + }, + { + "name" : "minecraft:element_60", + "id" : -71 + }, + { + "name" : "minecraft:element_61", + "id" : -72 + }, + { + "name" : "minecraft:element_62", + "id" : -73 + }, + { + "name" : "minecraft:element_63", + "id" : -74 + }, + { + "name" : "minecraft:element_64", + "id" : -75 + }, + { + "name" : "minecraft:element_65", + "id" : -76 + }, + { + "name" : "minecraft:element_66", + "id" : -77 + }, + { + "name" : "minecraft:element_67", + "id" : -78 + }, + { + "name" : "minecraft:element_68", + "id" : -79 + }, + { + "name" : "minecraft:element_69", + "id" : -80 + }, + { + "name" : "minecraft:element_7", + "id" : -18 + }, + { + "name" : "minecraft:element_70", + "id" : -81 + }, + { + "name" : "minecraft:element_71", + "id" : -82 + }, + { + "name" : "minecraft:element_72", + "id" : -83 + }, + { + "name" : "minecraft:element_73", + "id" : -84 + }, + { + "name" : "minecraft:element_74", + "id" : -85 + }, + { + "name" : "minecraft:element_75", + "id" : -86 + }, + { + "name" : "minecraft:element_76", + "id" : -87 + }, + { + "name" : "minecraft:element_77", + "id" : -88 + }, + { + "name" : "minecraft:element_78", + "id" : -89 + }, + { + "name" : "minecraft:element_79", + "id" : -90 + }, + { + "name" : "minecraft:element_8", + "id" : -19 + }, + { + "name" : "minecraft:element_80", + "id" : -91 + }, + { + "name" : "minecraft:element_81", + "id" : -92 + }, + { + "name" : "minecraft:element_82", + "id" : -93 + }, + { + "name" : "minecraft:element_83", + "id" : -94 + }, + { + "name" : "minecraft:element_84", + "id" : -95 + }, + { + "name" : "minecraft:element_85", + "id" : -96 + }, + { + "name" : "minecraft:element_86", + "id" : -97 + }, + { + "name" : "minecraft:element_87", + "id" : -98 + }, + { + "name" : "minecraft:element_88", + "id" : -99 + }, + { + "name" : "minecraft:element_89", + "id" : -100 + }, + { + "name" : "minecraft:element_9", + "id" : -20 + }, + { + "name" : "minecraft:element_90", + "id" : -101 + }, + { + "name" : "minecraft:element_91", + "id" : -102 + }, + { + "name" : "minecraft:element_92", + "id" : -103 + }, + { + "name" : "minecraft:element_93", + "id" : -104 + }, + { + "name" : "minecraft:element_94", + "id" : -105 + }, + { + "name" : "minecraft:element_95", + "id" : -106 + }, + { + "name" : "minecraft:element_96", + "id" : -107 + }, + { + "name" : "minecraft:element_97", + "id" : -108 + }, + { + "name" : "minecraft:element_98", + "id" : -109 + }, + { + "name" : "minecraft:element_99", + "id" : -110 + }, + { + "name" : "minecraft:elytra", + "id" : 569 + }, + { + "name" : "minecraft:emerald", + "id" : 517 + }, + { + "name" : "minecraft:emerald_block", + "id" : 133 + }, + { + "name" : "minecraft:emerald_ore", + "id" : 129 + }, + { + "name" : "minecraft:empty_map", + "id" : 520 + }, + { + "name" : "minecraft:enchanted_book", + "id" : 526 + }, + { + "name" : "minecraft:enchanted_golden_apple", + "id" : 259 + }, + { + "name" : "minecraft:enchanting_table", + "id" : 116 + }, + { + "name" : "minecraft:end_brick_stairs", + "id" : -178 + }, + { + "name" : "minecraft:end_bricks", + "id" : 206 + }, + { + "name" : "minecraft:end_crystal", + "id" : 662 + }, + { + "name" : "minecraft:end_gateway", + "id" : 209 + }, + { + "name" : "minecraft:end_portal", + "id" : 119 + }, + { + "name" : "minecraft:end_portal_frame", + "id" : 120 + }, + { + "name" : "minecraft:end_rod", + "id" : 208 + }, + { + "name" : "minecraft:end_stone", + "id" : 121 + }, + { + "name" : "minecraft:ender_chest", + "id" : 130 + }, + { + "name" : "minecraft:ender_dragon_spawn_egg", + "id" : 506 + }, + { + "name" : "minecraft:ender_eye", + "id" : 433 + }, + { + "name" : "minecraft:ender_pearl", + "id" : 422 + }, + { + "name" : "minecraft:enderman_spawn_egg", + "id" : 442 + }, + { + "name" : "minecraft:endermite_spawn_egg", + "id" : 460 + }, + { + "name" : "minecraft:evoker_spawn_egg", + "id" : 475 + }, + { + "name" : "minecraft:experience_bottle", + "id" : 513 + }, + { + "name" : "minecraft:exposed_copper", + "id" : -341 + }, + { + "name" : "minecraft:exposed_cut_copper", + "id" : -348 + }, + { + "name" : "minecraft:exposed_cut_copper_slab", + "id" : -362 + }, + { + "name" : "minecraft:exposed_cut_copper_stairs", + "id" : -355 + }, + { + "name" : "minecraft:exposed_double_cut_copper_slab", + "id" : -369 + }, + { + "name" : "minecraft:farmland", + "id" : 60 + }, + { + "name" : "minecraft:feather", + "id" : 327 + }, + { + "name" : "minecraft:fence", + "id" : 85 + }, + { + "name" : "minecraft:fence_gate", + "id" : 107 + }, + { + "name" : "minecraft:fermented_spider_eye", + "id" : 428 + }, + { + "name" : "minecraft:field_masoned_banner_pattern", + "id" : 590 + }, + { + "name" : "minecraft:filled_map", + "id" : 420 + }, + { + "name" : "minecraft:fire", + "id" : 51 + }, + { + "name" : "minecraft:fire_charge", + "id" : 514 + }, + { + "name" : "minecraft:firework_rocket", + "id" : 524 + }, + { + "name" : "minecraft:firework_star", + "id" : 525 + }, + { + "name" : "minecraft:fishing_rod", + "id" : 392 + }, + { + "name" : "minecraft:fletching_table", + "id" : -201 + }, + { + "name" : "minecraft:flint", + "id" : 356 + }, + { + "name" : "minecraft:flint_and_steel", + "id" : 299 + }, + { + "name" : "minecraft:flower_banner_pattern", + "id" : 586 + }, + { + "name" : "minecraft:flower_pot", + "id" : 519 + }, + { + "name" : "minecraft:flowering_azalea", + "id" : -338 + }, + { + "name" : "minecraft:flowing_lava", + "id" : 10 + }, + { + "name" : "minecraft:flowing_water", + "id" : 8 + }, + { + "name" : "minecraft:fox_spawn_egg", + "id" : 490 + }, + { + "name" : "minecraft:frame", + "id" : 518 + }, + { + "name" : "minecraft:frog_spawn", + "id" : -468 + }, + { + "name" : "minecraft:frog_spawn_egg", + "id" : 633 + }, + { + "name" : "minecraft:frosted_ice", + "id" : 207 + }, + { + "name" : "minecraft:furnace", + "id" : 61 + }, + { + "name" : "minecraft:ghast_spawn_egg", + "id" : 454 + }, + { + "name" : "minecraft:ghast_tear", + "id" : 424 + }, + { + "name" : "minecraft:gilded_blackstone", + "id" : -281 + }, + { + "name" : "minecraft:glass", + "id" : 20 + }, + { + "name" : "minecraft:glass_bottle", + "id" : 427 + }, + { + "name" : "minecraft:glass_pane", + "id" : 102 + }, + { + "name" : "minecraft:glistering_melon_slice", + "id" : 434 + }, + { + "name" : "minecraft:globe_banner_pattern", + "id" : 593 + }, + { + "name" : "minecraft:glow_berries", + "id" : 663 + }, + { + "name" : "minecraft:glow_frame", + "id" : 628 + }, + { + "name" : "minecraft:glow_ink_sac", + "id" : 508 + }, + { + "name" : "minecraft:glow_lichen", + "id" : -411 + }, + { + "name" : "minecraft:glow_squid_spawn_egg", + "id" : 503 + }, + { + "name" : "minecraft:glow_stick", + "id" : 606 + }, + { + "name" : "minecraft:glowingobsidian", + "id" : 246 + }, + { + "name" : "minecraft:glowstone", + "id" : 89 + }, + { + "name" : "minecraft:glowstone_dust", + "id" : 394 + }, + { + "name" : "minecraft:goat_horn", + "id" : 632 + }, + { + "name" : "minecraft:goat_spawn_egg", + "id" : 502 + }, + { + "name" : "minecraft:gold_block", + "id" : 41 + }, + { + "name" : "minecraft:gold_ingot", + "id" : 306 + }, + { + "name" : "minecraft:gold_nugget", + "id" : 425 + }, + { + "name" : "minecraft:gold_ore", + "id" : 14 + }, + { + "name" : "minecraft:golden_apple", + "id" : 258 + }, + { + "name" : "minecraft:golden_axe", + "id" : 325 + }, + { + "name" : "minecraft:golden_boots", + "id" : 354 + }, + { + "name" : "minecraft:golden_carrot", + "id" : 283 + }, + { + "name" : "minecraft:golden_chestplate", + "id" : 352 + }, + { + "name" : "minecraft:golden_helmet", + "id" : 351 + }, + { + "name" : "minecraft:golden_hoe", + "id" : 333 + }, + { + "name" : "minecraft:golden_horse_armor", + "id" : 537 + }, + { + "name" : "minecraft:golden_leggings", + "id" : 353 + }, + { + "name" : "minecraft:golden_pickaxe", + "id" : 324 + }, + { + "name" : "minecraft:golden_rail", + "id" : 27 + }, + { + "name" : "minecraft:golden_shovel", + "id" : 323 + }, + { + "name" : "minecraft:golden_sword", + "id" : 322 + }, + { + "name" : "minecraft:granite_stairs", + "id" : -169 + }, + { + "name" : "minecraft:grass", + "id" : 2 + }, + { + "name" : "minecraft:grass_path", + "id" : 198 + }, + { + "name" : "minecraft:gravel", + "id" : 13 + }, + { + "name" : "minecraft:gray_candle", + "id" : -420 + }, + { + "name" : "minecraft:gray_candle_cake", + "id" : -437 + }, + { + "name" : "minecraft:gray_dye", + "id" : 403 + }, + { + "name" : "minecraft:gray_glazed_terracotta", + "id" : 227 + }, + { + "name" : "minecraft:green_candle", + "id" : -426 + }, + { + "name" : "minecraft:green_candle_cake", + "id" : -443 + }, + { + "name" : "minecraft:green_dye", + "id" : 397 + }, + { + "name" : "minecraft:green_glazed_terracotta", + "id" : 233 + }, + { + "name" : "minecraft:grindstone", + "id" : -195 + }, + { + "name" : "minecraft:guardian_spawn_egg", + "id" : 461 + }, + { + "name" : "minecraft:gunpowder", + "id" : 328 + }, + { + "name" : "minecraft:hanging_roots", + "id" : -319 + }, + { + "name" : "minecraft:hard_glass", + "id" : 253 + }, + { + "name" : "minecraft:hard_glass_pane", + "id" : 190 + }, + { + "name" : "minecraft:hard_stained_glass", + "id" : 254 + }, + { + "name" : "minecraft:hard_stained_glass_pane", + "id" : 191 + }, + { + "name" : "minecraft:hardened_clay", + "id" : 172 + }, + { + "name" : "minecraft:hay_block", + "id" : 170 + }, + { + "name" : "minecraft:heart_of_the_sea", + "id" : 576 + }, + { + "name" : "minecraft:heavy_weighted_pressure_plate", + "id" : 148 + }, + { + "name" : "minecraft:hoglin_spawn_egg", + "id" : 496 + }, + { + "name" : "minecraft:honey_block", + "id" : -220 + }, + { + "name" : "minecraft:honey_bottle", + "id" : 597 + }, + { + "name" : "minecraft:honeycomb", + "id" : 596 + }, + { + "name" : "minecraft:honeycomb_block", + "id" : -221 + }, + { + "name" : "minecraft:hopper", + "id" : 532 + }, + { + "name" : "minecraft:hopper_minecart", + "id" : 531 + }, + { + "name" : "minecraft:horse_spawn_egg", + "id" : 458 + }, + { + "name" : "minecraft:husk_spawn_egg", + "id" : 463 + }, + { + "name" : "minecraft:ice", + "id" : 79 + }, + { + "name" : "minecraft:ice_bomb", + "id" : 600 + }, + { + "name" : "minecraft:infested_deepslate", + "id" : -454 + }, + { + "name" : "minecraft:info_update", + "id" : 248 + }, + { + "name" : "minecraft:info_update2", + "id" : 249 + }, + { + "name" : "minecraft:ink_sac", + "id" : 413 + }, + { + "name" : "minecraft:invisible_bedrock", + "id" : 95 + }, + { + "name" : "minecraft:iron_axe", + "id" : 298 + }, + { + "name" : "minecraft:iron_bars", + "id" : 101 + }, + { + "name" : "minecraft:iron_block", + "id" : 42 + }, + { + "name" : "minecraft:iron_boots", + "id" : 346 + }, + { + "name" : "minecraft:iron_chestplate", + "id" : 344 + }, + { + "name" : "minecraft:iron_door", + "id" : 372 + }, + { + "name" : "minecraft:iron_golem_spawn_egg", + "id" : 504 + }, + { + "name" : "minecraft:iron_helmet", + "id" : 343 + }, + { + "name" : "minecraft:iron_hoe", + "id" : 331 + }, + { + "name" : "minecraft:iron_horse_armor", + "id" : 536 + }, + { + "name" : "minecraft:iron_ingot", + "id" : 305 + }, + { + "name" : "minecraft:iron_leggings", + "id" : 345 + }, + { + "name" : "minecraft:iron_nugget", + "id" : 574 + }, + { + "name" : "minecraft:iron_ore", + "id" : 15 + }, + { + "name" : "minecraft:iron_pickaxe", + "id" : 297 + }, + { + "name" : "minecraft:iron_shovel", + "id" : 296 + }, + { + "name" : "minecraft:iron_sword", + "id" : 307 + }, + { + "name" : "minecraft:iron_trapdoor", + "id" : 167 + }, + { + "name" : "minecraft:item.acacia_door", + "id" : 196 + }, + { + "name" : "minecraft:item.bed", + "id" : 26 + }, + { + "name" : "minecraft:item.beetroot", + "id" : 244 + }, + { + "name" : "minecraft:item.birch_door", + "id" : 194 + }, + { + "name" : "minecraft:item.brewing_stand", + "id" : 117 + }, + { + "name" : "minecraft:item.cake", + "id" : 92 + }, + { + "name" : "minecraft:item.camera", + "id" : 242 + }, + { + "name" : "minecraft:item.campfire", + "id" : -209 + }, + { + "name" : "minecraft:item.cauldron", + "id" : 118 + }, + { + "name" : "minecraft:item.chain", + "id" : -286 + }, + { + "name" : "minecraft:item.crimson_door", + "id" : -244 + }, + { + "name" : "minecraft:item.dark_oak_door", + "id" : 197 + }, + { + "name" : "minecraft:item.flower_pot", + "id" : 140 + }, + { + "name" : "minecraft:item.frame", + "id" : 199 + }, + { + "name" : "minecraft:item.glow_frame", + "id" : -339 + }, + { + "name" : "minecraft:item.hopper", + "id" : 154 + }, + { + "name" : "minecraft:item.iron_door", + "id" : 71 + }, + { + "name" : "minecraft:item.jungle_door", + "id" : 195 + }, + { + "name" : "minecraft:item.kelp", + "id" : -138 + }, + { + "name" : "minecraft:item.mangrove_door", + "id" : -493 + }, + { + "name" : "minecraft:item.nether_sprouts", + "id" : -238 + }, + { + "name" : "minecraft:item.nether_wart", + "id" : 115 + }, + { + "name" : "minecraft:item.reeds", + "id" : 83 + }, + { + "name" : "minecraft:item.skull", + "id" : 144 + }, + { + "name" : "minecraft:item.soul_campfire", + "id" : -290 + }, + { + "name" : "minecraft:item.spruce_door", + "id" : 193 + }, + { + "name" : "minecraft:item.warped_door", + "id" : -245 + }, + { + "name" : "minecraft:item.wheat", + "id" : 59 + }, + { + "name" : "minecraft:item.wooden_door", + "id" : 64 + }, + { + "name" : "minecraft:jigsaw", + "id" : -211 + }, + { + "name" : "minecraft:jukebox", + "id" : 84 + }, + { + "name" : "minecraft:jungle_boat", + "id" : 377 + }, + { + "name" : "minecraft:jungle_button", + "id" : -143 + }, + { + "name" : "minecraft:jungle_chest_boat", + "id" : 645 + }, + { + "name" : "minecraft:jungle_door", + "id" : 560 + }, + { + "name" : "minecraft:jungle_fence_gate", + "id" : 185 + }, + { + "name" : "minecraft:jungle_hanging_sign", + "id" : -503 + }, + { + "name" : "minecraft:jungle_pressure_plate", + "id" : -153 + }, + { + "name" : "minecraft:jungle_sign", + "id" : 583 + }, + { + "name" : "minecraft:jungle_stairs", + "id" : 136 + }, + { + "name" : "minecraft:jungle_standing_sign", + "id" : -188 + }, + { + "name" : "minecraft:jungle_trapdoor", + "id" : -148 + }, + { + "name" : "minecraft:jungle_wall_sign", + "id" : -189 + }, + { + "name" : "minecraft:kelp", + "id" : 382 + }, + { + "name" : "minecraft:ladder", + "id" : 65 + }, + { + "name" : "minecraft:lantern", + "id" : -208 + }, + { + "name" : "minecraft:lapis_block", + "id" : 22 + }, + { + "name" : "minecraft:lapis_lazuli", + "id" : 414 + }, + { + "name" : "minecraft:lapis_ore", + "id" : 21 + }, + { + "name" : "minecraft:large_amethyst_bud", + "id" : -330 + }, + { + "name" : "minecraft:lava", + "id" : 11 + }, + { + "name" : "minecraft:lava_bucket", + "id" : 363 + }, + { + "name" : "minecraft:lava_cauldron", + "id" : -210 + }, + { + "name" : "minecraft:lead", + "id" : 552 + }, + { + "name" : "minecraft:leather", + "id" : 381 + }, + { + "name" : "minecraft:leather_boots", + "id" : 338 + }, + { + "name" : "minecraft:leather_chestplate", + "id" : 336 + }, + { + "name" : "minecraft:leather_helmet", + "id" : 335 + }, + { + "name" : "minecraft:leather_horse_armor", + "id" : 535 + }, + { + "name" : "minecraft:leather_leggings", + "id" : 337 + }, + { + "name" : "minecraft:leaves", + "id" : 18 + }, + { + "name" : "minecraft:leaves2", + "id" : 161 + }, + { + "name" : "minecraft:lectern", + "id" : -194 + }, + { + "name" : "minecraft:lever", + "id" : 69 + }, + { + "name" : "minecraft:light_block", + "id" : -215 + }, + { + "name" : "minecraft:light_blue_candle", + "id" : -416 + }, + { + "name" : "minecraft:light_blue_candle_cake", + "id" : -433 + }, + { + "name" : "minecraft:light_blue_dye", + "id" : 407 + }, + { + "name" : "minecraft:light_blue_glazed_terracotta", + "id" : 223 + }, + { + "name" : "minecraft:light_gray_candle", + "id" : -421 + }, + { + "name" : "minecraft:light_gray_candle_cake", + "id" : -438 + }, + { + "name" : "minecraft:light_gray_dye", + "id" : 402 + }, + { + "name" : "minecraft:light_weighted_pressure_plate", + "id" : 147 + }, + { + "name" : "minecraft:lightning_rod", + "id" : -312 + }, + { + "name" : "minecraft:lime_candle", + "id" : -418 + }, + { + "name" : "minecraft:lime_candle_cake", + "id" : -435 + }, + { + "name" : "minecraft:lime_dye", + "id" : 405 + }, + { + "name" : "minecraft:lime_glazed_terracotta", + "id" : 225 + }, + { + "name" : "minecraft:lingering_potion", + "id" : 567 + }, + { + "name" : "minecraft:lit_blast_furnace", + "id" : -214 + }, + { + "name" : "minecraft:lit_deepslate_redstone_ore", + "id" : -404 + }, + { + "name" : "minecraft:lit_furnace", + "id" : 62 + }, + { + "name" : "minecraft:lit_pumpkin", + "id" : 91 + }, + { + "name" : "minecraft:lit_redstone_lamp", + "id" : 124 + }, + { + "name" : "minecraft:lit_redstone_ore", + "id" : 74 + }, + { + "name" : "minecraft:lit_smoker", + "id" : -199 + }, + { + "name" : "minecraft:llama_spawn_egg", + "id" : 473 + }, + { + "name" : "minecraft:lodestone", + "id" : -222 + }, + { + "name" : "minecraft:lodestone_compass", + "id" : 607 + }, + { + "name" : "minecraft:log", + "id" : 17 + }, + { + "name" : "minecraft:log2", + "id" : 162 + }, + { + "name" : "minecraft:loom", + "id" : -204 + }, + { + "name" : "minecraft:magenta_candle", + "id" : -415 + }, + { + "name" : "minecraft:magenta_candle_cake", + "id" : -432 + }, + { + "name" : "minecraft:magenta_dye", + "id" : 408 + }, + { + "name" : "minecraft:magenta_glazed_terracotta", + "id" : 222 + }, + { + "name" : "minecraft:magma", + "id" : 213 + }, + { + "name" : "minecraft:magma_cream", + "id" : 430 + }, + { + "name" : "minecraft:magma_cube_spawn_egg", + "id" : 455 + }, + { + "name" : "minecraft:mangrove_boat", + "id" : 640 + }, + { + "name" : "minecraft:mangrove_button", + "id" : -487 + }, + { + "name" : "minecraft:mangrove_chest_boat", + "id" : 649 + }, + { + "name" : "minecraft:mangrove_door", + "id" : 638 + }, + { + "name" : "minecraft:mangrove_double_slab", + "id" : -499 + }, + { + "name" : "minecraft:mangrove_fence", + "id" : -491 + }, + { + "name" : "minecraft:mangrove_fence_gate", + "id" : -492 + }, + { + "name" : "minecraft:mangrove_hanging_sign", + "id" : -508 + }, + { + "name" : "minecraft:mangrove_leaves", + "id" : -472 + }, + { + "name" : "minecraft:mangrove_log", + "id" : -484 + }, + { + "name" : "minecraft:mangrove_planks", + "id" : -486 + }, + { + "name" : "minecraft:mangrove_pressure_plate", + "id" : -490 + }, + { + "name" : "minecraft:mangrove_propagule", + "id" : -474 + }, + { + "name" : "minecraft:mangrove_roots", + "id" : -482 + }, + { + "name" : "minecraft:mangrove_sign", + "id" : 639 + }, + { + "name" : "minecraft:mangrove_slab", + "id" : -489 + }, + { + "name" : "minecraft:mangrove_stairs", + "id" : -488 + }, + { + "name" : "minecraft:mangrove_standing_sign", + "id" : -494 + }, + { + "name" : "minecraft:mangrove_trapdoor", + "id" : -496 + }, + { + "name" : "minecraft:mangrove_wall_sign", + "id" : -495 + }, + { + "name" : "minecraft:mangrove_wood", + "id" : -497 + }, + { + "name" : "minecraft:medicine", + "id" : 604 + }, + { + "name" : "minecraft:medium_amethyst_bud", + "id" : -331 + }, + { + "name" : "minecraft:melon_block", + "id" : 103 + }, + { + "name" : "minecraft:melon_seeds", + "id" : 293 + }, + { + "name" : "minecraft:melon_slice", + "id" : 272 + }, + { + "name" : "minecraft:melon_stem", + "id" : 105 + }, + { + "name" : "minecraft:milk_bucket", + "id" : 361 + }, + { + "name" : "minecraft:minecart", + "id" : 370 + }, + { + "name" : "minecraft:mob_spawner", + "id" : 52 + }, + { + "name" : "minecraft:mojang_banner_pattern", + "id" : 589 + }, + { + "name" : "minecraft:monster_egg", + "id" : 97 + }, + { + "name" : "minecraft:mooshroom_spawn_egg", + "id" : 440 + }, + { + "name" : "minecraft:moss_block", + "id" : -320 + }, + { + "name" : "minecraft:moss_carpet", + "id" : -335 + }, + { + "name" : "minecraft:mossy_cobblestone", + "id" : 48 + }, + { + "name" : "minecraft:mossy_cobblestone_stairs", + "id" : -179 + }, + { + "name" : "minecraft:mossy_stone_brick_stairs", + "id" : -175 + }, + { + "name" : "minecraft:moving_block", + "id" : 250 + }, + { + "name" : "minecraft:mud", + "id" : -473 + }, + { + "name" : "minecraft:mud_brick_double_slab", + "id" : -479 + }, + { + "name" : "minecraft:mud_brick_slab", + "id" : -478 + }, + { + "name" : "minecraft:mud_brick_stairs", + "id" : -480 + }, + { + "name" : "minecraft:mud_brick_wall", + "id" : -481 + }, + { + "name" : "minecraft:mud_bricks", + "id" : -475 + }, + { + "name" : "minecraft:muddy_mangrove_roots", + "id" : -483 + }, + { + "name" : "minecraft:mule_spawn_egg", + "id" : 466 + }, + { + "name" : "minecraft:mushroom_stew", + "id" : 260 + }, + { + "name" : "minecraft:music_disc_11", + "id" : 549 + }, + { + "name" : "minecraft:music_disc_13", + "id" : 539 + }, + { + "name" : "minecraft:music_disc_5", + "id" : 641 + }, + { + "name" : "minecraft:music_disc_blocks", + "id" : 541 + }, + { + "name" : "minecraft:music_disc_cat", + "id" : 540 + }, + { + "name" : "minecraft:music_disc_chirp", + "id" : 542 + }, + { + "name" : "minecraft:music_disc_far", + "id" : 543 + }, + { + "name" : "minecraft:music_disc_mall", + "id" : 544 + }, + { + "name" : "minecraft:music_disc_mellohi", + "id" : 545 + }, + { + "name" : "minecraft:music_disc_otherside", + "id" : 631 + }, + { + "name" : "minecraft:music_disc_pigstep", + "id" : 625 + }, + { + "name" : "minecraft:music_disc_stal", + "id" : 546 + }, + { + "name" : "minecraft:music_disc_strad", + "id" : 547 + }, + { + "name" : "minecraft:music_disc_wait", + "id" : 550 + }, + { + "name" : "minecraft:music_disc_ward", + "id" : 548 + }, + { + "name" : "minecraft:mutton", + "id" : 555 + }, + { + "name" : "minecraft:mycelium", + "id" : 110 + }, + { + "name" : "minecraft:name_tag", + "id" : 553 + }, + { + "name" : "minecraft:nautilus_shell", + "id" : 575 + }, + { + "name" : "minecraft:nether_brick", + "id" : 112 + }, + { + "name" : "minecraft:nether_brick_fence", + "id" : 113 + }, + { + "name" : "minecraft:nether_brick_stairs", + "id" : 114 + }, + { + "name" : "minecraft:nether_gold_ore", + "id" : -288 + }, + { + "name" : "minecraft:nether_sprouts", + "id" : 626 + }, + { + "name" : "minecraft:nether_star", + "id" : 523 + }, + { + "name" : "minecraft:nether_wart", + "id" : 294 + }, + { + "name" : "minecraft:nether_wart_block", + "id" : 214 + }, + { + "name" : "minecraft:netherbrick", + "id" : 528 + }, + { + "name" : "minecraft:netherite_axe", + "id" : 612 + }, + { + "name" : "minecraft:netherite_block", + "id" : -270 + }, + { + "name" : "minecraft:netherite_boots", + "id" : 617 + }, + { + "name" : "minecraft:netherite_chestplate", + "id" : 615 + }, + { + "name" : "minecraft:netherite_helmet", + "id" : 614 + }, + { + "name" : "minecraft:netherite_hoe", + "id" : 613 + }, + { + "name" : "minecraft:netherite_ingot", + "id" : 608 + }, + { + "name" : "minecraft:netherite_leggings", + "id" : 616 + }, + { + "name" : "minecraft:netherite_pickaxe", + "id" : 611 + }, + { + "name" : "minecraft:netherite_scrap", + "id" : 618 + }, + { + "name" : "minecraft:netherite_shovel", + "id" : 610 + }, + { + "name" : "minecraft:netherite_sword", + "id" : 609 + }, + { + "name" : "minecraft:netherrack", + "id" : 87 + }, + { + "name" : "minecraft:netherreactor", + "id" : 247 + }, + { + "name" : "minecraft:normal_stone_stairs", + "id" : -180 + }, + { + "name" : "minecraft:noteblock", + "id" : 25 + }, + { + "name" : "minecraft:npc_spawn_egg", + "id" : 470 + }, + { + "name" : "minecraft:oak_boat", + "id" : 375 + }, + { + "name" : "minecraft:oak_chest_boat", + "id" : 643 + }, + { + "name" : "minecraft:oak_hanging_sign", + "id" : -500 + }, + { + "name" : "minecraft:oak_sign", + "id" : 358 + }, + { + "name" : "minecraft:oak_stairs", + "id" : 53 + }, + { + "name" : "minecraft:observer", + "id" : 251 + }, + { + "name" : "minecraft:obsidian", + "id" : 49 + }, + { + "name" : "minecraft:ocelot_spawn_egg", + "id" : 451 + }, + { + "name" : "minecraft:ochre_froglight", + "id" : -471 + }, + { + "name" : "minecraft:orange_candle", + "id" : -414 + }, + { + "name" : "minecraft:orange_candle_cake", + "id" : -431 + }, + { + "name" : "minecraft:orange_dye", + "id" : 409 + }, + { + "name" : "minecraft:orange_glazed_terracotta", + "id" : 221 + }, + { + "name" : "minecraft:oxidized_copper", + "id" : -343 + }, + { + "name" : "minecraft:oxidized_cut_copper", + "id" : -350 + }, + { + "name" : "minecraft:oxidized_cut_copper_slab", + "id" : -364 + }, + { + "name" : "minecraft:oxidized_cut_copper_stairs", + "id" : -357 + }, + { + "name" : "minecraft:oxidized_double_cut_copper_slab", + "id" : -371 + }, + { + "name" : "minecraft:packed_ice", + "id" : 174 + }, + { + "name" : "minecraft:packed_mud", + "id" : -477 + }, + { + "name" : "minecraft:painting", + "id" : 357 + }, + { + "name" : "minecraft:panda_spawn_egg", + "id" : 489 + }, + { + "name" : "minecraft:paper", + "id" : 386 + }, + { + "name" : "minecraft:parrot_spawn_egg", + "id" : 478 + }, + { + "name" : "minecraft:pearlescent_froglight", + "id" : -469 + }, + { + "name" : "minecraft:phantom_membrane", + "id" : 579 + }, + { + "name" : "minecraft:phantom_spawn_egg", + "id" : 486 + }, + { + "name" : "minecraft:pig_spawn_egg", + "id" : 437 + }, + { + "name" : "minecraft:piglin_banner_pattern", + "id" : 592 + }, + { + "name" : "minecraft:piglin_brute_spawn_egg", + "id" : 499 + }, + { + "name" : "minecraft:piglin_spawn_egg", + "id" : 497 + }, + { + "name" : "minecraft:pillager_spawn_egg", + "id" : 491 + }, + { + "name" : "minecraft:pink_candle", + "id" : -419 + }, + { + "name" : "minecraft:pink_candle_cake", + "id" : -436 + }, + { + "name" : "minecraft:pink_dye", + "id" : 404 + }, + { + "name" : "minecraft:pink_glazed_terracotta", + "id" : 226 + }, + { + "name" : "minecraft:piston", + "id" : 33 + }, + { + "name" : "minecraft:piston_arm_collision", + "id" : 34 + }, + { + "name" : "minecraft:planks", + "id" : 5 + }, + { + "name" : "minecraft:podzol", + "id" : 243 + }, + { + "name" : "minecraft:pointed_dripstone", + "id" : -308 + }, + { + "name" : "minecraft:poisonous_potato", + "id" : 282 + }, + { + "name" : "minecraft:polar_bear_spawn_egg", + "id" : 472 + }, + { + "name" : "minecraft:polished_andesite_stairs", + "id" : -174 + }, + { + "name" : "minecraft:polished_basalt", + "id" : -235 + }, + { + "name" : "minecraft:polished_blackstone", + "id" : -291 + }, + { + "name" : "minecraft:polished_blackstone_brick_double_slab", + "id" : -285 + }, + { + "name" : "minecraft:polished_blackstone_brick_slab", + "id" : -284 + }, + { + "name" : "minecraft:polished_blackstone_brick_stairs", + "id" : -275 + }, + { + "name" : "minecraft:polished_blackstone_brick_wall", + "id" : -278 + }, + { + "name" : "minecraft:polished_blackstone_bricks", + "id" : -274 + }, + { + "name" : "minecraft:polished_blackstone_button", + "id" : -296 + }, + { + "name" : "minecraft:polished_blackstone_double_slab", + "id" : -294 + }, + { + "name" : "minecraft:polished_blackstone_pressure_plate", + "id" : -295 + }, + { + "name" : "minecraft:polished_blackstone_slab", + "id" : -293 + }, + { + "name" : "minecraft:polished_blackstone_stairs", + "id" : -292 + }, + { + "name" : "minecraft:polished_blackstone_wall", + "id" : -297 + }, + { + "name" : "minecraft:polished_deepslate", + "id" : -383 + }, + { + "name" : "minecraft:polished_deepslate_double_slab", + "id" : -397 + }, + { + "name" : "minecraft:polished_deepslate_slab", + "id" : -384 + }, + { + "name" : "minecraft:polished_deepslate_stairs", + "id" : -385 + }, + { + "name" : "minecraft:polished_deepslate_wall", + "id" : -386 + }, + { + "name" : "minecraft:polished_diorite_stairs", + "id" : -173 + }, + { + "name" : "minecraft:polished_granite_stairs", + "id" : -172 + }, + { + "name" : "minecraft:popped_chorus_fruit", + "id" : 564 + }, + { + "name" : "minecraft:porkchop", + "id" : 262 + }, + { + "name" : "minecraft:portal", + "id" : 90 + }, + { + "name" : "minecraft:potato", + "id" : 280 + }, + { + "name" : "minecraft:potatoes", + "id" : 142 + }, + { + "name" : "minecraft:potion", + "id" : 426 + }, + { + "name" : "minecraft:powder_snow", + "id" : -306 + }, + { + "name" : "minecraft:powder_snow_bucket", + "id" : 368 + }, + { + "name" : "minecraft:powered_comparator", + "id" : 150 + }, + { + "name" : "minecraft:powered_repeater", + "id" : 94 + }, + { + "name" : "minecraft:prismarine", + "id" : 168 + }, + { + "name" : "minecraft:prismarine_bricks_stairs", + "id" : -4 + }, + { + "name" : "minecraft:prismarine_crystals", + "id" : 554 + }, + { + "name" : "minecraft:prismarine_shard", + "id" : 570 + }, + { + "name" : "minecraft:prismarine_stairs", + "id" : -2 + }, + { + "name" : "minecraft:pufferfish", + "id" : 267 + }, + { + "name" : "minecraft:pufferfish_bucket", + "id" : 367 + }, + { + "name" : "minecraft:pufferfish_spawn_egg", + "id" : 481 + }, + { + "name" : "minecraft:pumpkin", + "id" : 86 + }, + { + "name" : "minecraft:pumpkin_pie", + "id" : 284 + }, + { + "name" : "minecraft:pumpkin_seeds", + "id" : 292 + }, + { + "name" : "minecraft:pumpkin_stem", + "id" : 104 + }, + { + "name" : "minecraft:purple_candle", + "id" : -423 + }, + { + "name" : "minecraft:purple_candle_cake", + "id" : -440 + }, + { + "name" : "minecraft:purple_dye", + "id" : 400 + }, + { + "name" : "minecraft:purple_glazed_terracotta", + "id" : 219 + }, + { + "name" : "minecraft:purpur_block", + "id" : 201 + }, + { + "name" : "minecraft:purpur_stairs", + "id" : 203 + }, + { + "name" : "minecraft:quartz", + "id" : 529 + }, + { + "name" : "minecraft:quartz_block", + "id" : 155 + }, + { + "name" : "minecraft:quartz_bricks", + "id" : -304 + }, + { + "name" : "minecraft:quartz_ore", + "id" : 153 + }, + { + "name" : "minecraft:quartz_stairs", + "id" : 156 + }, + { + "name" : "minecraft:rabbit", + "id" : 288 + }, + { + "name" : "minecraft:rabbit_foot", + "id" : 533 + }, + { + "name" : "minecraft:rabbit_hide", + "id" : 534 + }, + { + "name" : "minecraft:rabbit_spawn_egg", + "id" : 459 + }, + { + "name" : "minecraft:rabbit_stew", + "id" : 290 + }, + { + "name" : "minecraft:rail", + "id" : 66 + }, + { + "name" : "minecraft:rapid_fertilizer", + "id" : 602 + }, + { + "name" : "minecraft:ravager_spawn_egg", + "id" : 493 + }, + { + "name" : "minecraft:raw_copper", + "id" : 512 + }, + { + "name" : "minecraft:raw_copper_block", + "id" : -452 + }, + { + "name" : "minecraft:raw_gold", + "id" : 511 + }, + { + "name" : "minecraft:raw_gold_block", + "id" : -453 + }, + { + "name" : "minecraft:raw_iron", + "id" : 510 + }, + { + "name" : "minecraft:raw_iron_block", + "id" : -451 + }, + { + "name" : "minecraft:recovery_compass", + "id" : 651 + }, + { + "name" : "minecraft:red_candle", + "id" : -427 + }, + { + "name" : "minecraft:red_candle_cake", + "id" : -444 + }, + { + "name" : "minecraft:red_dye", + "id" : 396 + }, + { + "name" : "minecraft:red_flower", + "id" : 38 + }, + { + "name" : "minecraft:red_glazed_terracotta", + "id" : 234 + }, + { + "name" : "minecraft:red_mushroom", + "id" : 40 + }, + { + "name" : "minecraft:red_mushroom_block", + "id" : 100 + }, + { + "name" : "minecraft:red_nether_brick", + "id" : 215 + }, + { + "name" : "minecraft:red_nether_brick_stairs", + "id" : -184 + }, + { + "name" : "minecraft:red_sandstone", + "id" : 179 + }, + { + "name" : "minecraft:red_sandstone_stairs", + "id" : 180 + }, + { + "name" : "minecraft:redstone", + "id" : 373 + }, + { + "name" : "minecraft:redstone_block", + "id" : 152 + }, + { + "name" : "minecraft:redstone_lamp", + "id" : 123 + }, + { + "name" : "minecraft:redstone_ore", + "id" : 73 + }, + { + "name" : "minecraft:redstone_torch", + "id" : 76 + }, + { + "name" : "minecraft:redstone_wire", + "id" : 55 + }, + { + "name" : "minecraft:reinforced_deepslate", + "id" : -466 + }, + { + "name" : "minecraft:repeater", + "id" : 419 + }, + { + "name" : "minecraft:repeating_command_block", + "id" : 188 + }, + { + "name" : "minecraft:reserved6", + "id" : 255 + }, + { + "name" : "minecraft:respawn_anchor", + "id" : -272 + }, + { + "name" : "minecraft:rotten_flesh", + "id" : 277 + }, + { + "name" : "minecraft:saddle", + "id" : 371 + }, + { + "name" : "minecraft:salmon", + "id" : 265 + }, + { + "name" : "minecraft:salmon_bucket", + "id" : 365 + }, + { + "name" : "minecraft:salmon_spawn_egg", + "id" : 482 + }, + { + "name" : "minecraft:sand", + "id" : 12 + }, + { + "name" : "minecraft:sandstone", + "id" : 24 + }, + { + "name" : "minecraft:sandstone_stairs", + "id" : 128 + }, + { + "name" : "minecraft:sapling", + "id" : 6 + }, + { + "name" : "minecraft:scaffolding", + "id" : -165 + }, + { + "name" : "minecraft:sculk", + "id" : -458 + }, + { + "name" : "minecraft:sculk_catalyst", + "id" : -460 + }, + { + "name" : "minecraft:sculk_sensor", + "id" : -307 + }, + { + "name" : "minecraft:sculk_shrieker", + "id" : -461 + }, + { + "name" : "minecraft:sculk_vein", + "id" : -459 + }, + { + "name" : "minecraft:scute", + "id" : 577 + }, + { + "name" : "minecraft:sea_lantern", + "id" : 169 + }, + { + "name" : "minecraft:sea_pickle", + "id" : -156 + }, + { + "name" : "minecraft:seagrass", + "id" : -130 + }, + { + "name" : "minecraft:shears", + "id" : 421 + }, + { + "name" : "minecraft:sheep_spawn_egg", + "id" : 438 + }, + { + "name" : "minecraft:shield", + "id" : 355 + }, + { + "name" : "minecraft:shroomlight", + "id" : -230 + }, + { + "name" : "minecraft:shulker_box", + "id" : 218 + }, + { + "name" : "minecraft:shulker_shell", + "id" : 571 + }, + { + "name" : "minecraft:shulker_spawn_egg", + "id" : 469 + }, + { + "name" : "minecraft:silver_glazed_terracotta", + "id" : 228 + }, + { + "name" : "minecraft:silverfish_spawn_egg", + "id" : 443 + }, + { + "name" : "minecraft:skeleton_horse_spawn_egg", + "id" : 467 + }, + { + "name" : "minecraft:skeleton_spawn_egg", + "id" : 444 + }, + { + "name" : "minecraft:skull", + "id" : 521 + }, + { + "name" : "minecraft:skull_banner_pattern", + "id" : 588 + }, + { + "name" : "minecraft:slime", + "id" : 165 + }, + { + "name" : "minecraft:slime_ball", + "id" : 388 + }, + { + "name" : "minecraft:slime_spawn_egg", + "id" : 445 + }, + { + "name" : "minecraft:small_amethyst_bud", + "id" : -332 + }, + { + "name" : "minecraft:small_dripleaf_block", + "id" : -336 + }, + { + "name" : "minecraft:smithing_table", + "id" : -202 + }, + { + "name" : "minecraft:smoker", + "id" : -198 + }, + { + "name" : "minecraft:smooth_basalt", + "id" : -377 + }, + { + "name" : "minecraft:smooth_quartz_stairs", + "id" : -185 + }, + { + "name" : "minecraft:smooth_red_sandstone_stairs", + "id" : -176 + }, + { + "name" : "minecraft:smooth_sandstone_stairs", + "id" : -177 + }, + { + "name" : "minecraft:smooth_stone", + "id" : -183 + }, + { + "name" : "minecraft:sniffer_spawn_egg", + "id" : 500 + }, + { + "name" : "minecraft:snow", + "id" : 80 + }, + { + "name" : "minecraft:snow_golem_spawn_egg", + "id" : 505 + }, + { + "name" : "minecraft:snow_layer", + "id" : 78 + }, + { + "name" : "minecraft:snowball", + "id" : 374 + }, + { + "name" : "minecraft:soul_campfire", + "id" : 627 + }, + { + "name" : "minecraft:soul_fire", + "id" : -237 + }, + { + "name" : "minecraft:soul_lantern", + "id" : -269 + }, + { + "name" : "minecraft:soul_sand", + "id" : 88 + }, + { + "name" : "minecraft:soul_soil", + "id" : -236 + }, + { + "name" : "minecraft:soul_torch", + "id" : -268 + }, + { + "name" : "minecraft:sparkler", + "id" : 605 + }, + { + "name" : "minecraft:spawn_egg", + "id" : 661 + }, + { + "name" : "minecraft:spider_eye", + "id" : 278 + }, + { + "name" : "minecraft:spider_spawn_egg", + "id" : 446 + }, + { + "name" : "minecraft:splash_potion", + "id" : 566 + }, + { + "name" : "minecraft:sponge", + "id" : 19 + }, + { + "name" : "minecraft:spore_blossom", + "id" : -321 + }, + { + "name" : "minecraft:spruce_boat", + "id" : 378 + }, + { + "name" : "minecraft:spruce_button", + "id" : -144 + }, + { + "name" : "minecraft:spruce_chest_boat", + "id" : 646 + }, + { + "name" : "minecraft:spruce_door", + "id" : 558 + }, + { + "name" : "minecraft:spruce_fence_gate", + "id" : 183 + }, + { + "name" : "minecraft:spruce_hanging_sign", + "id" : -501 + }, + { + "name" : "minecraft:spruce_pressure_plate", + "id" : -154 + }, + { + "name" : "minecraft:spruce_sign", + "id" : 581 + }, + { + "name" : "minecraft:spruce_stairs", + "id" : 134 + }, + { + "name" : "minecraft:spruce_standing_sign", + "id" : -181 + }, + { + "name" : "minecraft:spruce_trapdoor", + "id" : -149 + }, + { + "name" : "minecraft:spruce_wall_sign", + "id" : -182 + }, + { + "name" : "minecraft:spyglass", + "id" : 630 + }, + { + "name" : "minecraft:squid_spawn_egg", + "id" : 450 + }, + { + "name" : "minecraft:stained_glass", + "id" : 241 + }, + { + "name" : "minecraft:stained_glass_pane", + "id" : 160 + }, + { + "name" : "minecraft:stained_hardened_clay", + "id" : 159 + }, + { + "name" : "minecraft:standing_banner", + "id" : 176 + }, + { + "name" : "minecraft:standing_sign", + "id" : 63 + }, + { + "name" : "minecraft:stick", + "id" : 320 + }, + { + "name" : "minecraft:sticky_piston", + "id" : 29 + }, + { + "name" : "minecraft:sticky_piston_arm_collision", + "id" : -217 + }, + { + "name" : "minecraft:stone", + "id" : 1 + }, + { + "name" : "minecraft:stone_axe", + "id" : 315 + }, + { + "name" : "minecraft:stone_block_slab", + "id" : 44 + }, + { + "name" : "minecraft:stone_block_slab2", + "id" : 182 + }, + { + "name" : "minecraft:stone_block_slab3", + "id" : -162 + }, + { + "name" : "minecraft:stone_block_slab4", + "id" : -166 + }, + { + "name" : "minecraft:stone_brick_stairs", + "id" : 109 + }, + { + "name" : "minecraft:stone_button", + "id" : 77 + }, + { + "name" : "minecraft:stone_hoe", + "id" : 330 + }, + { + "name" : "minecraft:stone_pickaxe", + "id" : 314 + }, + { + "name" : "minecraft:stone_pressure_plate", + "id" : 70 + }, + { + "name" : "minecraft:stone_shovel", + "id" : 313 + }, + { + "name" : "minecraft:stone_stairs", + "id" : 67 + }, + { + "name" : "minecraft:stone_sword", + "id" : 312 + }, + { + "name" : "minecraft:stonebrick", + "id" : 98 + }, + { + "name" : "minecraft:stonecutter", + "id" : 245 + }, + { + "name" : "minecraft:stonecutter_block", + "id" : -197 + }, + { + "name" : "minecraft:stray_spawn_egg", + "id" : 462 + }, + { + "name" : "minecraft:strider_spawn_egg", + "id" : 495 + }, + { + "name" : "minecraft:string", + "id" : 326 + }, + { + "name" : "minecraft:stripped_acacia_log", + "id" : -8 + }, + { + "name" : "minecraft:stripped_bamboo_block", + "id" : -528 + }, + { + "name" : "minecraft:stripped_birch_log", + "id" : -6 + }, + { + "name" : "minecraft:stripped_crimson_hyphae", + "id" : -300 + }, + { + "name" : "minecraft:stripped_crimson_stem", + "id" : -240 + }, + { + "name" : "minecraft:stripped_dark_oak_log", + "id" : -9 + }, + { + "name" : "minecraft:stripped_jungle_log", + "id" : -7 + }, + { + "name" : "minecraft:stripped_mangrove_log", + "id" : -485 + }, + { + "name" : "minecraft:stripped_mangrove_wood", + "id" : -498 + }, + { + "name" : "minecraft:stripped_oak_log", + "id" : -10 + }, + { + "name" : "minecraft:stripped_spruce_log", + "id" : -5 + }, + { + "name" : "minecraft:stripped_warped_hyphae", + "id" : -301 + }, + { + "name" : "minecraft:stripped_warped_stem", + "id" : -241 + }, + { + "name" : "minecraft:structure_block", + "id" : 252 + }, + { + "name" : "minecraft:structure_void", + "id" : 217 + }, + { + "name" : "minecraft:sugar", + "id" : 416 + }, + { + "name" : "minecraft:sugar_cane", + "id" : 385 + }, + { + "name" : "minecraft:suspicious_stew", + "id" : 595 + }, + { + "name" : "minecraft:sweet_berries", + "id" : 287 + }, + { + "name" : "minecraft:sweet_berry_bush", + "id" : -207 + }, + { + "name" : "minecraft:tadpole_bucket", + "id" : 635 + }, + { + "name" : "minecraft:tadpole_spawn_egg", + "id" : 634 + }, + { + "name" : "minecraft:tallgrass", + "id" : 31 + }, + { + "name" : "minecraft:target", + "id" : -239 + }, + { + "name" : "minecraft:tinted_glass", + "id" : -334 + }, + { + "name" : "minecraft:tnt", + "id" : 46 + }, + { + "name" : "minecraft:tnt_minecart", + "id" : 530 + }, + { + "name" : "minecraft:torch", + "id" : 50 + }, + { + "name" : "minecraft:totem_of_undying", + "id" : 573 + }, + { + "name" : "minecraft:trader_llama_spawn_egg", + "id" : 653 + }, + { + "name" : "minecraft:trapdoor", + "id" : 96 + }, + { + "name" : "minecraft:trapped_chest", + "id" : 146 + }, + { + "name" : "minecraft:trident", + "id" : 551 + }, + { + "name" : "minecraft:trip_wire", + "id" : 132 + }, + { + "name" : "minecraft:tripwire_hook", + "id" : 131 + }, + { + "name" : "minecraft:tropical_fish", + "id" : 266 + }, + { + "name" : "minecraft:tropical_fish_bucket", + "id" : 366 + }, + { + "name" : "minecraft:tropical_fish_spawn_egg", + "id" : 479 + }, + { + "name" : "minecraft:tuff", + "id" : -333 + }, + { + "name" : "minecraft:turtle_egg", + "id" : -159 + }, + { + "name" : "minecraft:turtle_helmet", + "id" : 578 + }, + { + "name" : "minecraft:turtle_spawn_egg", + "id" : 485 + }, + { + "name" : "minecraft:twisting_vines", + "id" : -287 + }, + { + "name" : "minecraft:underwater_torch", + "id" : 239 + }, + { + "name" : "minecraft:undyed_shulker_box", + "id" : 205 + }, + { + "name" : "minecraft:unknown", + "id" : -305 + }, + { + "name" : "minecraft:unlit_redstone_torch", + "id" : 75 + }, + { + "name" : "minecraft:unpowered_comparator", + "id" : 149 + }, + { + "name" : "minecraft:unpowered_repeater", + "id" : 93 + }, + { + "name" : "minecraft:verdant_froglight", + "id" : -470 + }, + { + "name" : "minecraft:vex_spawn_egg", + "id" : 476 + }, + { + "name" : "minecraft:villager_spawn_egg", + "id" : 449 + }, + { + "name" : "minecraft:vindicator_spawn_egg", + "id" : 474 + }, + { + "name" : "minecraft:vine", + "id" : 106 + }, + { + "name" : "minecraft:wall_banner", + "id" : 177 + }, + { + "name" : "minecraft:wall_sign", + "id" : 68 + }, + { + "name" : "minecraft:wandering_trader_spawn_egg", + "id" : 492 + }, + { + "name" : "minecraft:warden_spawn_egg", + "id" : 637 + }, + { + "name" : "minecraft:warped_button", + "id" : -261 + }, + { + "name" : "minecraft:warped_door", + "id" : 622 + }, + { + "name" : "minecraft:warped_double_slab", + "id" : -267 + }, + { + "name" : "minecraft:warped_fence", + "id" : -257 + }, + { + "name" : "minecraft:warped_fence_gate", + "id" : -259 + }, + { + "name" : "minecraft:warped_fungus", + "id" : -229 + }, + { + "name" : "minecraft:warped_fungus_on_a_stick", + "id" : 623 + }, + { + "name" : "minecraft:warped_hanging_sign", + "id" : -507 + }, + { + "name" : "minecraft:warped_hyphae", + "id" : -298 + }, + { + "name" : "minecraft:warped_nylium", + "id" : -233 + }, + { + "name" : "minecraft:warped_planks", + "id" : -243 + }, + { + "name" : "minecraft:warped_pressure_plate", + "id" : -263 + }, + { + "name" : "minecraft:warped_roots", + "id" : -224 + }, + { + "name" : "minecraft:warped_sign", + "id" : 620 + }, + { + "name" : "minecraft:warped_slab", + "id" : -265 + }, + { + "name" : "minecraft:warped_stairs", + "id" : -255 + }, + { + "name" : "minecraft:warped_standing_sign", + "id" : -251 + }, + { + "name" : "minecraft:warped_stem", + "id" : -226 + }, + { + "name" : "minecraft:warped_trapdoor", + "id" : -247 + }, + { + "name" : "minecraft:warped_wall_sign", + "id" : -253 + }, + { + "name" : "minecraft:warped_wart_block", + "id" : -227 + }, + { + "name" : "minecraft:water", + "id" : 9 + }, + { + "name" : "minecraft:water_bucket", + "id" : 362 + }, + { + "name" : "minecraft:waterlily", + "id" : 111 + }, + { + "name" : "minecraft:waxed_copper", + "id" : -344 + }, + { + "name" : "minecraft:waxed_cut_copper", + "id" : -351 + }, + { + "name" : "minecraft:waxed_cut_copper_slab", + "id" : -365 + }, + { + "name" : "minecraft:waxed_cut_copper_stairs", + "id" : -358 + }, + { + "name" : "minecraft:waxed_double_cut_copper_slab", + "id" : -372 + }, + { + "name" : "minecraft:waxed_exposed_copper", + "id" : -345 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper", + "id" : -352 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_slab", + "id" : -366 + }, + { + "name" : "minecraft:waxed_exposed_cut_copper_stairs", + "id" : -359 + }, + { + "name" : "minecraft:waxed_exposed_double_cut_copper_slab", + "id" : -373 + }, + { + "name" : "minecraft:waxed_oxidized_copper", + "id" : -446 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper", + "id" : -447 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_slab", + "id" : -449 + }, + { + "name" : "minecraft:waxed_oxidized_cut_copper_stairs", + "id" : -448 + }, + { + "name" : "minecraft:waxed_oxidized_double_cut_copper_slab", + "id" : -450 + }, + { + "name" : "minecraft:waxed_weathered_copper", + "id" : -346 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper", + "id" : -353 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_slab", + "id" : -367 + }, + { + "name" : "minecraft:waxed_weathered_cut_copper_stairs", + "id" : -360 + }, + { + "name" : "minecraft:waxed_weathered_double_cut_copper_slab", + "id" : -374 + }, + { + "name" : "minecraft:weathered_copper", + "id" : -342 + }, + { + "name" : "minecraft:weathered_cut_copper", + "id" : -349 + }, + { + "name" : "minecraft:weathered_cut_copper_slab", + "id" : -363 + }, + { + "name" : "minecraft:weathered_cut_copper_stairs", + "id" : -356 + }, + { + "name" : "minecraft:weathered_double_cut_copper_slab", + "id" : -370 + }, + { + "name" : "minecraft:web", + "id" : 30 + }, + { + "name" : "minecraft:weeping_vines", + "id" : -231 + }, + { + "name" : "minecraft:wheat", + "id" : 334 + }, + { + "name" : "minecraft:wheat_seeds", + "id" : 291 + }, + { + "name" : "minecraft:white_candle", + "id" : -413 + }, + { + "name" : "minecraft:white_candle_cake", + "id" : -430 + }, + { + "name" : "minecraft:white_dye", + "id" : 410 + }, + { + "name" : "minecraft:white_glazed_terracotta", + "id" : 220 + }, + { + "name" : "minecraft:witch_spawn_egg", + "id" : 452 + }, + { + "name" : "minecraft:wither_rose", + "id" : -216 + }, + { + "name" : "minecraft:wither_skeleton_spawn_egg", + "id" : 464 + }, + { + "name" : "minecraft:wither_spawn_egg", + "id" : 507 + }, + { + "name" : "minecraft:wolf_spawn_egg", + "id" : 439 + }, + { + "name" : "minecraft:wood", + "id" : -212 + }, + { + "name" : "minecraft:wooden_axe", + "id" : 311 + }, + { + "name" : "minecraft:wooden_button", + "id" : 143 + }, + { + "name" : "minecraft:wooden_door", + "id" : 359 + }, + { + "name" : "minecraft:wooden_hoe", + "id" : 329 + }, + { + "name" : "minecraft:wooden_pickaxe", + "id" : 310 + }, + { + "name" : "minecraft:wooden_pressure_plate", + "id" : 72 + }, + { + "name" : "minecraft:wooden_shovel", + "id" : 309 + }, + { + "name" : "minecraft:wooden_slab", + "id" : 158 + }, + { + "name" : "minecraft:wooden_sword", + "id" : 308 + }, + { + "name" : "minecraft:wool", + "id" : 35 + }, + { + "name" : "minecraft:writable_book", + "id" : 515 + }, + { + "name" : "minecraft:written_book", + "id" : 516 + }, + { + "name" : "minecraft:yellow_candle", + "id" : -417 + }, + { + "name" : "minecraft:yellow_candle_cake", + "id" : -434 + }, + { + "name" : "minecraft:yellow_dye", + "id" : 406 + }, + { + "name" : "minecraft:yellow_flower", + "id" : 37 + }, + { + "name" : "minecraft:yellow_glazed_terracotta", + "id" : 224 + }, + { + "name" : "minecraft:zoglin_spawn_egg", + "id" : 498 + }, + { + "name" : "minecraft:zombie_horse_spawn_egg", + "id" : 468 + }, + { + "name" : "minecraft:zombie_pigman_spawn_egg", + "id" : 448 + }, + { + "name" : "minecraft:zombie_spawn_egg", + "id" : 447 + }, + { + "name" : "minecraft:zombie_villager_spawn_egg", + "id" : 477 + } +] \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 683ee39c9..3b9e47905 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ netty = "4.1.80.Final" guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" -protocol = "2.9.15-20230106.005737-3" +protocol = "2.9.16-20230205.181702-1" raknet = "1.6.28-20220125.214016-6" mcauthlib = "d9d773e" mcprotocollib = "1.19.3-20230107.194116-10" @@ -85,7 +85,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" } mcprotocollib = { group = "com.github.steveice10", name = "mcprotocollib", version.ref = "mcprotocollib" } packetlib = { group = "com.github.steveice10", name = "packetlib", version.ref = "packetlib" } -protocol = { group = "com.nukkitx.protocol", name = "bedrock-v560", version.ref = "protocol" } +protocol = { group = "com.nukkitx.protocol", name = "bedrock-v567", version.ref = "protocol" } raknet = { group = "com.nukkitx.network", name = "raknet", version.ref = "raknet" } sponge-api = { group = "org.spongepowered", name = "spongeapi", version.ref = "sponge" } terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" } From f07f532d8e49aa72d92bd50ea240fd60a3ed9f1d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:09:16 -0500 Subject: [PATCH 32/38] Update supported versions in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc6e21b1a..b36235e36 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.51 and Minecraft Java 1.19.3. +### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.60 and Minecraft Java 1.19.3. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. From 6ee3d4ed962d8d476d0052ae15af695620fbf4d1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:14:06 -0500 Subject: [PATCH 33/38] Fix #3537 --- .../geysermc/geyser/entity/type/player/PlayerEntity.java | 4 ++++ .../java/org/geysermc/geyser/scoreboard/Scoreboard.java | 3 ++- .../main/java/org/geysermc/geyser/scoreboard/Team.java | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 3501eb296..c77892ac5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -119,6 +119,8 @@ public class PlayerEntity extends LivingEntity { setBelowNameText(objective); } + // Update in case this entity has been despawned, then respawned + this.nametag = this.username; // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now updateDisplayName(session.getWorldCache().getScoreboard().getTeamFor(username)); @@ -339,6 +341,8 @@ public class PlayerEntity extends LivingEntity { this.nametag = this.username; } + System.out.println(this.username + " " + this.nametag + " " + needsUpdate); + if (needsUpdate) { dirtyMetadata.put(EntityData.NAMETAG, this.nametag); } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index f97693a62..a7178ac8d 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -352,7 +352,8 @@ public final class Scoreboard { // With the player's lack of a team in visibility checks updateEntityNames(remove, remove.getEntities(), true); for (String name : remove.getEntities()) { - playerToTeam.remove(name, remove); + // 1.19.3 Mojmap Scoreboard#removePlayerTeam(PlayerTeam) + playerToTeam.remove(name); } session.removeCommandEnum("Geyser_Teams", remove.getId()); diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index 34db4a048..7738f5f42 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -65,7 +65,14 @@ public final class Team { if (entities.add(name)) { added.add(name); } - scoreboard.getPlayerToTeam().put(name, this); + scoreboard.getPlayerToTeam().compute(name, (player, oldTeam) -> { + if (oldTeam != null) { + // Remove old team from this map, and from the set of players of the old team. + // Java 1.19.3 Mojmap: Scoreboard#addPlayerToTeam calls #removePlayerFromTeam + oldTeam.entities.remove(player); + } + return this; + }); } if (added.isEmpty()) { From bad077de53d8c435fd85ce55fe39541efbf8356e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:15:02 -0500 Subject: [PATCH 34/38] oops --- .../org/geysermc/geyser/entity/type/player/PlayerEntity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index c77892ac5..74a55144e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -341,8 +341,6 @@ public class PlayerEntity extends LivingEntity { this.nametag = this.username; } - System.out.println(this.username + " " + this.nametag + " " + needsUpdate); - if (needsUpdate) { dirtyMetadata.put(EntityData.NAMETAG, this.nametag); } From 3d97188db0e24f5efef647b29db35cbe5cf20699 Mon Sep 17 00:00:00 2001 From: pop4959 Date: Thu, 9 Feb 2023 08:19:17 -0800 Subject: [PATCH 35/38] Add property to show scoreboard logs (#3539) * property to show scoreboard logs * cache system property --- .../java/org/geysermc/geyser/scoreboard/Scoreboard.java | 6 +++++- .../protocol/java/scoreboard/JavaSetScoreTranslator.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index a7178ac8d..193b4b6ac 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -48,6 +48,8 @@ import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.geyser.scoreboard.UpdateType.*; public final class Scoreboard { + private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true")); + private final GeyserSession session; private final GeyserLogger logger; @Getter @@ -134,7 +136,9 @@ public final class Scoreboard { public Team registerNewTeam(String teamName, String[] players) { Team team = teams.get(teamName); if (team != null) { - logger.info(GeyserLocale.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); + if (SHOW_SCOREBOARD_LOGS) { + logger.info(GeyserLocale.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); + } return team; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java index 41b978a86..7fa86b120 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java @@ -44,6 +44,8 @@ import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = ClientboundSetScorePacket.class) public class JavaSetScoreTranslator extends PacketTranslator { + private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true")); + private final GeyserLogger logger; public JavaSetScoreTranslator() { @@ -58,7 +60,9 @@ public class JavaSetScoreTranslator extends PacketTranslator Date: Thu, 9 Feb 2023 17:31:00 +0100 Subject: [PATCH 36/38] Moved the base api to a separate repo --- api/base/build.gradle.kts | 7 - .../main/java/org/geysermc/api/Geyser.java | 95 ------------- .../java/org/geysermc/api/GeyserApiBase.java | 130 ------------------ .../geysermc/api/connection/Connection.java | 121 ---------------- .../geysermc/api/util/BedrockPlatform.java | 73 ---------- .../java/org/geysermc/api/util/InputMode.java | 49 ------- .../java/org/geysermc/api/util/UiProfile.java | 45 ------ api/build.gradle.kts | 7 + api/geyser/build.gradle.kts | 14 -- .../org/geysermc/geyser/api/GeyserApi.java | 0 .../geysermc/geyser/api/command/Command.java | 0 .../geyser/api/command/CommandExecutor.java | 0 .../geyser/api/command/CommandSource.java | 0 .../api/connection/GeyserConnection.java | 0 .../geysermc/geyser/api/event/EventBus.java | 0 .../geyser/api/event/EventRegistrar.java | 0 .../geyser/api/event/EventSubscriber.java | 0 .../geyser/api/event/ExtensionEventBus.java | 0 .../api/event/ExtensionEventSubscriber.java | 0 .../api/event/connection/ConnectionEvent.java | 0 .../downstream/ServerDefineCommandsEvent.java | 0 .../lifecycle/GeyserDefineCommandsEvent.java | 0 .../GeyserDefineCustomItemsEvent.java | 0 .../GeyserLoadResourcePacksEvent.java | 0 .../lifecycle/GeyserPostInitializeEvent.java | 0 .../lifecycle/GeyserPreInitializeEvent.java | 0 .../event/lifecycle/GeyserShutdownEvent.java | 0 .../geyser/api/extension/Extension.java | 0 .../api/extension/ExtensionDescription.java | 0 .../geyser/api/extension/ExtensionLoader.java | 0 .../geyser/api/extension/ExtensionLogger.java | 0 .../api/extension/ExtensionManager.java | 0 .../InvalidDescriptionException.java | 0 .../exception/InvalidExtensionException.java | 0 .../api/item/custom/CustomItemData.java | 0 .../api/item/custom/CustomItemOptions.java | 0 .../api/item/custom/CustomRenderOffsets.java | 0 .../item/custom/NonVanillaCustomItemData.java | 0 .../geysermc/geyser/api/network/AuthType.java | 0 .../geyser/api/network/BedrockListener.java | 0 .../geyser/api/network/RemoteServer.java | 0 .../geysermc/geyser/api/util/TriState.java | 0 .../kotlin/geyser.api-conventions.gradle.kts | 9 -- build.gradle.kts | 18 +-- core/build.gradle.kts | 2 +- gradle/libs.versions.toml | 11 +- settings.gradle.kts | 3 - 47 files changed, 19 insertions(+), 565 deletions(-) delete mode 100644 api/base/build.gradle.kts delete mode 100644 api/base/src/main/java/org/geysermc/api/Geyser.java delete mode 100644 api/base/src/main/java/org/geysermc/api/GeyserApiBase.java delete mode 100644 api/base/src/main/java/org/geysermc/api/connection/Connection.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/InputMode.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/UiProfile.java create mode 100644 api/build.gradle.kts delete mode 100644 api/geyser/build.gradle.kts rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/GeyserApi.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/command/Command.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/command/CommandSource.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/EventBus.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/Extension.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/network/AuthType.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java (100%) rename api/{geyser => }/src/main/java/org/geysermc/geyser/api/util/TriState.java (100%) delete mode 100644 build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts deleted file mode 100644 index 6b6fb8f46..000000000 --- a/api/base/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -dependencies { - api(libs.cumulus) - api(libs.events) { - exclude(group = "com.google.guava", module = "guava") - exclude(group = "org.lanternpowered", module = "lmbda") - } -} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java deleted file mode 100644 index 7543d1661..000000000 --- a/api/base/src/main/java/org/geysermc/api/Geyser.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * General API class for Geyser. - */ -@NonNull -public class Geyser { - private static GeyserApiBase api; - - /** - * Returns the base api. - * - * @return the base api - */ - @NonNull - public static GeyserApiBase api() { - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } - - return api; - } - - /** - * Returns the api of the given type. - * - * @param apiClass the api class - * @param the type - * @return the api of the given type - */ - @SuppressWarnings("unchecked") - public static T api(@NonNull Class apiClass) { - if (apiClass.isInstance(api)) { - return (T) api; - } - - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } else { - throw new RuntimeException("Api was not an instance of " + apiClass + "! Was " + api.getClass().getCanonicalName()); - } - } - - /** - * Registers the given api type. The api cannot be - * registered if {@link #isRegistered()} is true as - * an api has already been specified. - * - * @param api the api - */ - public static void set(@NonNull GeyserApiBase api) { - if (Geyser.api != null) { - throw new RuntimeException("Cannot redefine already registered api!"); - } - - Geyser.api = api; - } - - /** - * Gets if the api has been registered and - * is ready for usage. - * - * @return if the api has been registered - */ - public static boolean isRegistered() { - return api != null; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java deleted file mode 100644 index a845e37fd..000000000 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.connection.Connection; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.List; -import java.util.UUID; - -/** - * The base API class. - */ -public interface GeyserApiBase { - /** - * Gets the connection from the given UUID, if applicable. The player must be logged in to the Java server - * for this to return a non-null value. - * - * @param uuid the UUID of the connection - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByUuid(@NonNull UUID uuid); - - /** - * Gets the connection from the given XUID, if applicable. This method only works for online connections. - * - * @param xuid the XUID of the session - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByXuid(@NonNull String xuid); - - /** - * Method to determine if the given online player is a Bedrock player. - * - * @param uuid the uuid of the online player - * @return true if the given online player is a Bedrock player - */ - boolean isBedrockPlayer(@NonNull UUID uuid); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull Form form); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder); - - /** - * Transfer the given connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param uuid the uuid of the connection - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull UUID uuid, @NonNull String address, @IntRange(from = 0, to = 65535) int port); - - - /** - * Returns all the online connections. - */ - @NonNull - List onlineConnections(); - - /** - * Returns the amount of online connections. - */ - int onlineConnectionsCount(); - - /** - * Returns the prefix used by Floodgate. Will be null when the auth-type isn't Floodgate. - */ - @MonotonicNonNull - String usernamePrefix(); - - /** - * Returns the major API version. Bumped whenever a significant breaking change or feature addition is added. - */ - default int majorApiVersion() { - return 1; - } - - /** - * Returns the minor API version. May be bumped for new API additions. - */ - default int minorApiVersion() { - return 0; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java deleted file mode 100644 index 1cd7a9d13..000000000 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.connection; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.util.BedrockPlatform; -import org.geysermc.api.util.InputMode; -import org.geysermc.api.util.UiProfile; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.UUID; - -/** - * Represents a player connection. - */ -public interface Connection { - /** - * Returns the bedrock name of the connection. - */ - @NonNull String bedrockUsername(); - - /** - * Returns the java name of the connection. - */ - @MonotonicNonNull - String javaUsername(); - - /** - * Returns the UUID of the connection. - */ - @MonotonicNonNull - UUID javaUuid(); - - /** - * Returns the XUID of the connection. - */ - @NonNull String xuid(); - - /** - * Returns the version of the Bedrock client. - */ - @NonNull String version(); - - /** - * Returns the platform that the connection is playing on. - */ - @NonNull BedrockPlatform platform(); - - /** - * Returns the language code of the connection. - */ - @NonNull String languageCode(); - - /** - * Returns the User Interface Profile of the connection. - */ - @NonNull UiProfile uiProfile(); - - /** - * Returns the Input Mode of the Bedrock client. - */ - @NonNull InputMode inputMode(); - - /** - * Returns whether the connection is linked. - * This will always return false when the auth-type isn't Floodgate. - */ - boolean isLinked(); - - /** - * Sends a form to the connection and opens it. - * - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull Form form); - - /** - * Sends a form to the connection and opens it. - * - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull FormBuilder formBuilder); - - /** - * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); -} diff --git a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java b/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java deleted file mode 100644 index 15d0da027..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum BedrockPlatform { - UNKNOWN("Unknown"), - GOOGLE("Android"), - IOS("iOS"), - OSX("macOS"), - AMAZON("Amazon"), - GEARVR("Gear VR"), - HOLOLENS("Hololens"), - UWP("Windows"), - WIN32("Windows x86"), - DEDICATED("Dedicated"), - TVOS("Apple TV"), - PS4("PS4"), - NX("Switch"), - XBOX("Xbox One"), - WINDOWS_PHONE("Windows Phone"); - - private static final BedrockPlatform[] VALUES = values(); - - private final String displayName; - - BedrockPlatform(String displayName) { - this.displayName = displayName; - } - - /** - * Get the BedrockPlatform from the identifier. - * - * @param id the BedrockPlatform identifier - * @return The BedrockPlatform or {@link #UNKNOWN} if the platform wasn't found - */ - @NonNull - public static BedrockPlatform fromId(int id) { - return id < VALUES.length ? VALUES[id] : VALUES[0]; - } - - /** - * @return friendly display name of platform. - */ - @Override - public String toString() { - return displayName; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/util/InputMode.java b/api/base/src/main/java/org/geysermc/api/util/InputMode.java deleted file mode 100644 index 70346ffa5..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/InputMode.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum InputMode { - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, - CONTROLLER, - VR; - - private static final InputMode[] VALUES = values(); - - /** - * Get the InputMode from the identifier. - * - * @param id the InputMode identifier - * @return The InputMode or {@link #UNKNOWN} if the mode wasn't found - */ - @NonNull - public static InputMode fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java b/api/base/src/main/java/org/geysermc/api/util/UiProfile.java deleted file mode 100644 index cddb97260..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum UiProfile { - CLASSIC, POCKET; - - private static final UiProfile[] VALUES = values(); - - /** - * Get the UiProfile from the identifier. - * - * @param id the UiProfile identifier - * @return The UiProfile or {@link #CLASSIC} if the profile wasn't found - */ - @NonNull - public static UiProfile fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 000000000..c0ed242b6 --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("geyser.publish-conventions") +} + +dependencies { + api(libs.base.api) +} \ No newline at end of file diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts deleted file mode 100644 index dcde85337..000000000 --- a/api/geyser/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id("geyser.api-conventions") -} - -dependencies { - api(projects.api) -} - -publishing { - publications.named("mavenJava") { - groupId = rootProject.group as String + ".geyser" - artifactId = "api" - } -} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java rename to api/src/main/java/org/geysermc/geyser/api/GeyserApi.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/command/Command.java rename to api/src/main/java/org/geysermc/geyser/api/command/Command.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java rename to api/src/main/java/org/geysermc/geyser/api/command/CommandExecutor.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/command/CommandSource.java rename to api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java rename to api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java b/api/src/main/java/org/geysermc/geyser/api/event/EventBus.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/EventBus.java rename to api/src/main/java/org/geysermc/geyser/api/event/EventBus.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java b/api/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java rename to api/src/main/java/org/geysermc/geyser/api/event/EventRegistrar.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java b/api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java rename to api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java b/api/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java rename to api/src/main/java/org/geysermc/geyser/api/event/ExtensionEventBus.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java b/api/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java rename to api/src/main/java/org/geysermc/geyser/api/event/ExtensionEventSubscriber.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostInitializeEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreInitializeEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserShutdownEvent.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java rename to api/src/main/java/org/geysermc/geyser/api/extension/Extension.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java rename to api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java rename to api/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java rename to api/src/main/java/org/geysermc/geyser/api/extension/ExtensionLogger.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java rename to api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java rename to api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java rename to api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/CustomRenderOffsets.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java b/api/src/main/java/org/geysermc/geyser/api/network/AuthType.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/network/AuthType.java rename to api/src/main/java/org/geysermc/geyser/api/network/AuthType.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java rename to api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java b/api/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java rename to api/src/main/java/org/geysermc/geyser/api/network/RemoteServer.java diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java b/api/src/main/java/org/geysermc/geyser/api/util/TriState.java similarity index 100% rename from api/geyser/src/main/java/org/geysermc/geyser/api/util/TriState.java rename to api/src/main/java/org/geysermc/geyser/api/util/TriState.java diff --git a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts deleted file mode 100644 index 7c8f9a3d7..000000000 --- a/build-logic/src/main/kotlin/geyser.api-conventions.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("geyser.publish-conventions") -} - -tasks { - shadowJar { - archiveBaseName.set(archiveBaseName.get() + "-api") - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 06c2e987b..4304811ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } allprojects { - group = "org.geysermc" + group = "org.geysermc.geyser" version = "2.1.0-SNAPSHOT" description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers." @@ -23,8 +23,6 @@ val platforms = setOf( projects.velocity ).map { it.dependencyProject } -val api: Project = projects.api.dependencyProject - subprojects { apply { plugin("java-library") @@ -32,16 +30,8 @@ subprojects { plugin("geyser.build-logic") } - val relativePath = projectDir.relativeTo(rootProject.projectDir).path - - if (relativePath.contains("api")) { - plugins.apply("geyser.api-conventions") - } else { - group = rootProject.group as String + ".geyser" - when (this) { - in platforms -> plugins.apply("geyser.platform-conventions") - api -> plugins.apply("geyser.publish-conventions") - else -> plugins.apply("geyser.base-conventions") - } + when (this) { + in platforms -> plugins.apply("geyser.platform-conventions") + else -> plugins.apply("geyser.base-conventions") } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 994325ea0..e5e64ca69 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } dependencies { - api(projects.geyserApi) + api(projects.api) api(projects.common) // Jackson JSON and YAML serialization diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4f14d7b0..7339cdffd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,7 @@ [versions] +base-api = "1.0.0-SNAPSHOT" +cumulus = "1.1.1" +events = "1.0-SNAPSHOT" jackson = "2.14.0" fastutil = "8.5.2" netty = "4.1.80.Final" @@ -14,8 +17,6 @@ adventure = "4.12.0-20220629.025215-9" adventure-platform = "4.1.2" junit = "4.13.1" checkerframework = "3.19.0" -cumulus = "1.1.1" -events = "1.0-SNAPSHOT" log4j = "2.17.1" jline = "3.21.0" terminalconsoleappender = "1.2.0" @@ -31,6 +32,10 @@ fabric-loader = "0.14.8" fabric-api = "0.58.5+1.19.1" [libraries] +base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-api" } +cumulus = { group = "org.geysermc.cumulus", name = "cumulus", version.ref = "cumulus" } +events = { group = "org.geysermc.event", name = "events", version.ref = "events" } + jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" } jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" } jackson-dataformat-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" } @@ -74,8 +79,6 @@ adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", bungeecord-proxy = { group = "com.github.SpigotMC.BungeeCord", name = "bungeecord-proxy", version.ref = "bungeecord" } checker-qual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerframework" } commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" } -cumulus = { group = "org.geysermc.cumulus", name = "cumulus", version.ref = "cumulus" } -events = { group = "org.geysermc.event", name = "events", version.ref = "events" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } junit = { group = "junit", name = "junit", version.ref = "junit" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6f212d3ac..e9c7641e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -62,7 +62,6 @@ rootProject.name = "geyser-parent" include(":ap") include(":api") -include(":geyser-api") include(":bungeecord") include(":fabric") include(":spigot") @@ -73,8 +72,6 @@ include(":common") include(":core") // Specify project dirs -project(":api").projectDir = file("api/base") -project(":geyser-api").projectDir = file("api/geyser") project(":bungeecord").projectDir = file("bootstrap/bungeecord") project(":fabric").projectDir = file("bootstrap/fabric") project(":spigot").projectDir = file("bootstrap/spigot") From c60a4d07598e85cb00395e74751f526618074b46 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+camotoy@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:15:33 +0100 Subject: [PATCH 37/38] Upcoming API changes for emote event --- .../api/event/bedrock/BedrockEmoteEvent.java | 66 ++++++++++++++ .../downstream/ServerDefineCommandsEvent.java | 7 +- .../event/java/ServerDefineCommandsEvent.java | 85 +++++++++++++++++++ 3 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/bedrock/BedrockEmoteEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/java/ServerDefineCommandsEvent.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/BedrockEmoteEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/BedrockEmoteEvent.java new file mode 100644 index 000000000..efe3f12d6 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/BedrockEmoteEvent.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.bedrock; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Cancellable; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; + +/** + * Called whenever a Bedrock player performs an emote on their end, before it is broadcasted to the rest of the server. + */ +public final class BedrockEmoteEvent extends ConnectionEvent implements Cancellable { + private final String emoteId; + private boolean cancelled; + + public BedrockEmoteEvent(@NonNull GeyserConnection connection, @NonNull String emoteId) { + super(connection); + this.emoteId = emoteId; + } + + /** + * @return the emote ID that the Bedrock player is attempting to perform. + */ + @NonNull + public String emoteId() { + return emoteId; + } + + /** + * @return the cancel status of this event. A Bedrock player will still play this emote on its end even if this + * event is cancelled, but other Bedrock players will not see. + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java index e46492b36..ff4a1d074 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/downstream/ServerDefineCommandsEvent.java @@ -32,11 +32,11 @@ import org.geysermc.geyser.api.event.connection.ConnectionEvent; import java.util.Set; + /** - * Called when the Java server defines the commands available on the server. - *
- * This event is mapped to the existence of Brigadier on the server. + * @deprecated please use {@link org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent}. */ +@Deprecated(forRemoval = true) public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable { private final Set commands; private boolean cancelled; @@ -67,6 +67,7 @@ public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancel this.cancelled = cancelled; } + @Deprecated(forRemoval = true) public interface CommandInfo { /** * Gets the name of the command. diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerDefineCommandsEvent.java new file mode 100644 index 000000000..299c9d6dd --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerDefineCommandsEvent.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.java; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Cancellable; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; + +import java.util.Set; + +/** + * Called when the Java server defines the commands available on the server. + *
+ * This event is mapped to the existence of Brigadier on the server. + */ +public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable { + private final Set commands; + private boolean cancelled; + + public ServerDefineCommandsEvent(@NonNull GeyserConnection connection, @NonNull Set commands) { + super(connection); + this.commands = commands; + } + + /** + * A collection of commands sent from the server. Any element in this collection can be removed, but no element can + * be added. + * + * @return a collection of the commands sent over + */ + @NonNull + public Set commands() { + return this.commands; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public interface CommandInfo { + /** + * Gets the name of the command. + * + * @return the name of the command + */ + String name(); + + /** + * Gets the description of the command. + * + * @return the description of the command + */ + String description(); + } +} From 9b3b2fba17ab6cbe621c016ebb01ab94ebbe77f0 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 10 Feb 2023 04:18:03 +0100 Subject: [PATCH 38/38] callout internet connection issues (#3544) --- core/src/main/java/org/geysermc/geyser/Constants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 46a1cb2ec..e8544f000 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -46,6 +46,7 @@ public final class Constants { try { wsUri = new URI("wss://api.geysermc.org/ws"); } catch (URISyntaxException e) { + GeyserImpl.getInstance().getLogger().error("Unable to resolve api.geysermc.org! Check your internet connection."); e.printStackTrace(); } GLOBAL_API_WS_URI = wsUri;