diff --git a/README.md b/README.md index 14bdb17a9..07f3df5aa 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,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.20.80 - 1.21.2 and Minecraft Java 1.21 +### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.3 and Minecraft Java 1.21 ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. @@ -42,7 +42,7 @@ There are a few things Geyser is unable to support due to various differences be 3. Run `gradlew build` and locate to `bootstrap/build` folder. ## Contributing -Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if +Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if you're interested in helping out with Geyser. ## Libraries Used: diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java index 3c3853ed8..1193a52b3 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -36,6 +36,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; @@ -43,6 +44,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; @AllArgsConstructor public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener { @@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List future.complete(event); } })); - ProxyPingEvent event = future.join(); + + ProxyPingEvent event; + + try { + event = future.get(100, TimeUnit.MILLISECONDS); + } catch (Throwable cause) { + String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : ""; + GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause); + return null; + } + ServerPing response = event.getResponse(); return new GeyserPingInfo( response.getDescriptionComponent().toLegacyText(), diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 88cc74691..9ee182edd 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -55,7 +55,11 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.event.lifecycle.*; +import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; @@ -85,7 +89,13 @@ import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.AssetUtils; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.util.DimensionUtils; +import org.geysermc.geyser.util.Metrics; +import org.geysermc.geyser.util.NewsHandler; +import org.geysermc.geyser.util.VersionCheckUtils; +import org.geysermc.geyser.util.WebUtils; import org.geysermc.mcprotocollib.network.tcp.TcpSession; import java.io.File; @@ -97,11 +107,19 @@ import java.net.UnknownHostException; import java.nio.file.Path; import java.security.Key; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -645,16 +663,11 @@ public class GeyserImpl implements GeyserApi { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done")); } - scheduledThread.shutdown(); - geyserServer.shutdown(); - if (skinUploader != null) { - skinUploader.close(); - } - newsHandler.shutdown(); - - if (this.erosionUnixListener != null) { - this.erosionUnixListener.close(); - } + runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown); + runIfNonNull(geyserServer, GeyserServer::shutdown); + runIfNonNull(skinUploader, FloodgateSkinUploader::close); + runIfNonNull(newsHandler, NewsHandler::shutdown); + runIfNonNull(erosionUnixListener, UnixSocketClientListener::close); Registries.RESOURCE_PACKS.get().clear(); @@ -833,6 +846,12 @@ public class GeyserImpl implements GeyserApi { } } + private void runIfNonNull(T nullable, Consumer consumer) { + if (nullable != null) { + consumer.accept(nullable); + } + } + private void scheduleRefreshTokensWrite() { scheduledThread.execute(() -> { // Ensure all writes are handled on the same thread diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index b2aa03995..674026054 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -327,6 +327,7 @@ public final class EntityDefinitions { TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase) .type(EntityType.TEXT_DISPLAY) .identifier("minecraft:armor_stand") + .offset(-0.5f) .addTranslator(MetadataType.CHAT, TextDisplayEntity::setText) .addTranslator(null) // Line width .addTranslator(null) // Background color diff --git a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java index a4a0df8b8..3b543a943 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java @@ -50,6 +50,7 @@ public enum GeyserAttributeType { ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), + BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f), // Bedrock Attributes ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java index ff5604c19..8b47ce1ed 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java @@ -38,7 +38,17 @@ import java.util.UUID; // Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition public class TextDisplayEntity extends DisplayBaseEntity { public TextDisplayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { - super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + super(session, entityId, geyserId, uuid, definition, position.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { + super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java index 8baba6f00..69b19b1b9 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -31,7 +31,6 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; @@ -39,13 +38,9 @@ import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; -import java.util.Set; import java.util.UUID; public class ParrotEntity extends TameableEntity { - // Note: is the same as chicken. Reuse? - private static final Set TAMING_FOOD = Set.of(Items.WHEAT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.BEETROOT_SEEDS); - public ParrotEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } 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 45fea4d48..dc0545cee 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 @@ -64,6 +64,11 @@ public class SessionPlayerEntity extends PlayerEntity { */ @Getter protected final Map attributes = new Object2ObjectOpenHashMap<>(); + /** + * Java-only attribute + */ + @Getter + private double blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue(); /** * Used in PlayerInputTranslator for movement checks. */ @@ -232,6 +237,8 @@ public class SessionPlayerEntity extends PlayerEntity { protected void updateAttribute(Attribute javaAttribute, List newAttributes) { if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) { session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute)); + } else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) { + this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute); } else { super.updateAttribute(javaAttribute, newAttributes); } @@ -295,6 +302,7 @@ public class SessionPlayerEntity extends PlayerEntity { public void resetAttributes() { attributes.clear(); maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue(); + blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(geyserId); diff --git a/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java b/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java new file mode 100644 index 000000000..c41d14396 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/type/BedrockRequiresTagItem.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.item.type; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +public interface BedrockRequiresTagItem { + + void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder); +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java index 9c637afde..2e7848318 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FireworkRocketItem.java @@ -27,6 +27,8 @@ package org.geysermc.geyser.item.type; import it.unimi.dsi.fastutil.ints.IntArrays; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtList; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtType; @@ -41,7 +43,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks; import java.util.ArrayList; import java.util.List; -public class FireworkRocketItem extends Item { +public class FireworkRocketItem extends Item implements BedrockRequiresTagItem { public FireworkRocketItem(String javaIdentifier, Builder builder) { super(javaIdentifier, builder); } @@ -58,14 +60,16 @@ public class FireworkRocketItem extends Item { fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration()); List explosions = fireworks.getExplosions(); - if (explosions.isEmpty()) { - return; + if (!explosions.isEmpty()) { + List explosionNbt = new ArrayList<>(); + for (Fireworks.FireworkExplosion explosion : explosions) { + explosionNbt.add(translateExplosionToBedrock(explosion)); + } + fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt); + } else { + // This is the default firework + fireworksNbt.put("Explosions", NbtList.EMPTY); } - List explosionNbt = new ArrayList<>(); - for (Fireworks.FireworkExplosion explosion : explosions) { - explosionNbt.add(translateExplosionToBedrock(explosion)); - } - fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt); builder.putCompound("Fireworks", fireworksNbt.build()); } @@ -138,4 +142,20 @@ public class FireworkRocketItem extends Item { return null; } } + + @Override + public void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder) { + if (components != null) { + Fireworks fireworks = components.get(DataComponentType.FIREWORKS); + if (fireworks != null) { + // Already translated + return; + } + } + + NbtMapBuilder fireworksNbt = NbtMap.builder(); + fireworksNbt.putByte("Flight", (byte) 1); + fireworksNbt.put("Explosions", NbtList.EMPTY); + builder.putCompound("Fireworks", fireworksNbt.build()); + } } 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 c79ef365d..8f3f00021 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -73,7 +73,9 @@ public final class GameProtocol { SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() .minecraftVersion("1.21.0/1.21.1") .build())); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.21.2/1.21.3") + .build()); } /** diff --git a/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java index 69ac974cc..4e60d60e4 100644 --- a/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java @@ -35,10 +35,10 @@ import java.net.InetSocketAddress; public interface IGeyserPingPassthrough { /** - * Get the MOTD of the server displayed on the multiplayer screen + * Gets the ping information, including the MOTD and player count, from the server * * @param inetSocketAddress the ip address of the client pinging the server - * @return string of the MOTD + * @return the ping information */ @Nullable GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java index 2c033edc7..8e42887ff 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java @@ -80,7 +80,6 @@ public class CreativeItemRegistryPopulator { private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map definitions) { int count = 1; int damage = 0; - int bedrockBlockRuntimeId; NbtMap tag = null; String identifier = itemNode.get("id").textValue(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index a6fa164c1..0a9c93980 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -199,7 +199,13 @@ public class CustomItemRegistryPopulator { computeThrowableProperties(componentBuilder); } - computeRenderOffsets(false, customItemData, componentBuilder); + // Hardcoded on Java, and should extend to the custom item + boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) + || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) + || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) + || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) + ); + computeRenderOffsets(isHat, customItemData, componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index aad5e494d..2c97fe13c 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 @@ -167,6 +167,7 @@ public class ItemRegistryPopulator { Map javaItemToMapping = new Object2ObjectOpenHashMap<>(); List creativeItems = new ArrayList<>(); + Set noBlockDefinitions = new ObjectOpenHashSet<>(); AtomicInteger creativeNetId = new AtomicInteger(); CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> { @@ -187,6 +188,9 @@ public class ItemRegistryPopulator { bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition()); } } + } else { + // Item mappings should also NOT have a block definition for these. + noBlockDefinitions.add(item.getDefinition().getIdentifier()); } }); @@ -254,7 +258,12 @@ public class ItemRegistryPopulator { } else { // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1); - if (aValidBedrockBlockId != -1 || customBlockItemOverride != null) { + if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) { + // Fallback + if (!noBlockDefinitions.contains(entry.getValue().getBedrockIdentifier())) { + bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId); + } + } else { // As of 1.16.220, every item requires a block runtime ID attached to it. // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls. // However, in order for some visuals and crafting to work, we need to send the first matching block state diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index 437b8223a..8a2c77f28 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.registry.type; import it.unimi.dsi.fastutil.Pair; import lombok.Builder; import lombok.EqualsAndHashCode; +import lombok.ToString; import lombok.Value; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; @@ -42,6 +43,7 @@ import java.util.List; @Value @Builder @EqualsAndHashCode +@ToString public class ItemMapping { public static final ItemMapping AIR = new ItemMapping( "minecraft:air", 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 6524e1ddc..3affa12cf 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 @@ -141,6 +141,10 @@ public class EntityCache { return playerEntities.values(); } + public void removeAllPlayerEntities() { + playerEntities.clear(); + } + public void addBossBar(UUID uuid, BossBar bossBar) { bossBars.put(uuid, bossBar); bossBar.addBossBar(); 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 a40a1156d..0eec39b0b 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 @@ -243,8 +243,16 @@ public class SkullCache { } public void clear() { + for (Skull skull : skulls.values()) { + if (skull.entity != null) { + skull.entity.despawnEntity(); + } + } skulls.clear(); inRangeSkulls.clear(); + for (SkullPlayerEntity skull : unusedSkullEntities) { + skull.despawnEntity(); + } unusedSkullEntities.clear(); totalSkullEntities = 0; lastPlayerPosition = null; 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 8eb715560..fb5137b05 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 @@ -201,4 +201,4 @@ public final class WorldCache { public String removeActiveRecord(Vector3i pos) { return this.activeRecords.remove(pos); } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 6a781dcb8..e9527872a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -45,6 +45,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.Rarity; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.item.type.BedrockRequiresTagItem; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -148,6 +149,12 @@ public final class ItemTranslator { if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true; } + // Fixes fireworks crafting recipe: they always contain a tag + // TODO remove once all items have their default components + if (javaItem instanceof BedrockRequiresTagItem requiresTagItem) { + requiresTagItem.addRequiredNbt(session, components, nbtBuilder); + } + Rarity rarity = javaItem.rarity(); boolean enchantmentGlint = javaItem.glint(); if (components != null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java index fb71a84cc..703c0954c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java @@ -42,10 +42,9 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { List items = javaNbt.getList("Items", NbtType.COMPOUND); if (items != null) { - int i = 1; for (NbtMap itemTag : items) { - bedrockNbt.put("Item" + i, getItem(session, itemTag)); - i++; + int slot = itemTag.getByte("Slot") + 1; + bedrockNbt.put("Item" + slot, getItem(session, itemTag)); } } } @@ -55,8 +54,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { if (mapping == null) { mapping = ItemMapping.AIR; } - NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getByte("Count"), mapping.getBedrockData()); - tagBuilder.put("tag", NbtMap.builder().build()); // I don't think this is necessary... - Camo, 1.20.5/1.20.80 + NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getInt("count"), mapping.getBedrockData()); return tagBuilder.build(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 534a89e23..1e4c82da1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -70,7 +70,11 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; @@ -78,7 +82,11 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractActio import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; -import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; import java.util.List; import java.util.concurrent.TimeUnit; @@ -90,11 +98,6 @@ import java.util.concurrent.TimeUnit; @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { - private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f; - private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49; - private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36; - private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f; - @Override public void translate(GeyserSession session, InventoryTransactionPacket packet) { if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) { @@ -243,17 +246,11 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) { restoreCorrectBlock(session, blockPos, packet); return; } @@ -262,26 +259,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator - (creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { - restoreCorrectBlock(session, blockPos, packet); - return; - } - Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f); - // Vanilla check - if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0) - .distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { - // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. - restoreCorrectBlock(session, blockPos, packet); - return; - } - // More recent vanilla check (as of 1.18.2) double clickDistanceX = clickPositionFullX - blockCenter.getX(); double clickDistanceY = clickPositionFullY - blockCenter.getY(); double clickDistanceZ = clickPositionFullZ - blockCenter.getZ(); @@ -303,13 +282,16 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator MAXIMUM_BLOCK_DESTROYING_DISTANCE) { + playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight()); + + if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) { restoreCorrectBlock(session, packet.getBlockPosition(), packet); return; } @@ -550,6 +528,28 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator(BlockPos) + float minX = packetBlockPosition.getX(); + float minY = packetBlockPosition.getY(); + float minZ = packetBlockPosition.getZ(); + float maxX = packetBlockPosition.getX() + 1; + float maxY = packetBlockPosition.getY() + 1; + float maxZ = packetBlockPosition.getZ() + 1; + + // AABB#distanceToSqr + float diffX = Math.max(Math.max(minX - playerPosition.getX(), playerPosition.getX() - maxX), 0); + float diffY = Math.max(Math.max(minY - playerPosition.getY(), playerPosition.getY() - maxY), 0); + float diffZ = Math.max(Math.max(minZ - playerPosition.getZ(), playerPosition.getZ() - maxZ), 0); + return ((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) < (additionalRangeCheck * additionalRangeCheck); + } + /** * Restore the correct block state from the server without updating the chunk cache. * @@ -696,4 +696,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -70,7 +84,7 @@ public class BedrockActionTranslator extends PacketTranslator { // Respawn process is finished and the server and client are both OK with respawning. EntityEventPacket eventPacket = new EntityEventPacket(); eventPacket.setRuntimeEntityId(entity.getGeyserId()); @@ -88,16 +102,16 @@ public class BedrockActionTranslator extends PacketTranslator { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); session.sendDownstreamGamePacket(startSwimPacket); session.setSwimming(true); } - break; - case STOP_SWIMMING: + } + case STOP_SWIMMING -> { // Prevent packet spam when Bedrock players are crawling near the edge of a block if (!session.getCollisionManager().mustPlayerCrawlHere()) { ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); @@ -105,56 +119,55 @@ public class BedrockActionTranslator extends PacketTranslator { // Otherwise gliding will not work in creative ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false); session.sendDownstreamGamePacket(playerAbilitiesPacket); - case STOP_GLIDE: - ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); - session.sendDownstreamGamePacket(glidePacket); - break; - case START_SNEAK: + sendPlayerGlideToggle(session, entity); + } + case STOP_GLIDE -> sendPlayerGlideToggle(session, entity); + case START_SNEAK -> { ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING); session.sendDownstreamGamePacket(startSneakPacket); session.startSneaking(); - break; - case STOP_SNEAK: + } + case STOP_SNEAK -> { ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING); session.sendDownstreamGamePacket(stopSneakPacket); session.stopSneaking(); - break; - case START_SPRINT: + } + case START_SPRINT -> { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); session.sendDownstreamGamePacket(startSprintPacket); session.setSprinting(true); } - break; - case STOP_SPRINT: + } + case STOP_SPRINT -> { if (!entity.getFlag(EntityFlag.SWIMMING)) { ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); session.sendDownstreamGamePacket(stopSprintPacket); } session.setSprinting(false); - break; - case DROP_ITEM: + } + case DROP_ITEM -> { ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, - vector, Direction.VALUES[packet.getFace()], 0); + vector, Direction.VALUES[packet.getFace()], 0); session.sendDownstreamGamePacket(dropItemPacket); - break; - case STOP_SLEEP: + } + case STOP_SLEEP -> { ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED); session.sendDownstreamGamePacket(stopSleepingPacket); - break; - case START_BREAK: { - // Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021 - if (session.getGameMode() == GameMode.CREATIVE) { + } + case START_BREAK -> { + // Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021 + if (session.getGameMode() == GameMode.CREATIVE) { break; } - + // Start the block breaking animation int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector); LevelEventPacket startBreak = new LevelEventPacket(); @@ -180,18 +193,20 @@ public class BedrockActionTranslator extends PacketTranslator { if (session.getGameMode() == GameMode.CREATIVE) { break; } @@ -201,52 +216,48 @@ public class BedrockActionTranslator extends PacketTranslator= (breakTime+=2) * 50) { + if (timeSinceStart >= (breakTime += 2) * 50) { // Play break sound and particle LevelEventPacket effectPacket = new LevelEventPacket(); effectPacket.setPosition(vectorFloat); effectPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK); effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock)); session.sendUpstreamPacket(effectPacket); - + // Break the block ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, - vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); + vector, direction, session.getWorldCache().nextPredictionSequence()); session.sendDownstreamGamePacket(finishBreakingPacket); session.setBlockBreakStartTime(0); break; } } - + // Update the break time in the event that player conditions changed (jumping, effects applied) + LevelEventPacket updateBreak = new LevelEventPacket(); + updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK); + updateBreak.setPosition(vectorFloat); updateBreak.setData((int) (65535 / breakTime)); session.sendUpstreamPacket(updateBreak); - break; - case ABORT_BREAK: + } + case ABORT_BREAK -> { if (session.getGameMode() != GameMode.CREATIVE) { // As of 1.16.210: item frame items are taken out here. // Survival also sends START_BREAK, but by attaching our process here adventure mode also works Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector); if (itemFrameEntity != null) { ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(), - InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); + InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); session.sendDownstreamGamePacket(interactPacket); break; } @@ -260,25 +271,23 @@ public class BedrockActionTranslator extends PacketTranslator { + } + case DIMENSION_CHANGE_SUCCESS -> { //sometimes the client doesn't feel like loading PlayStatusPacket spawnPacket = new PlayStatusPacket(); spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); session.sendUpstreamPacket(spawnPacket); - attributesPacket = new UpdateAttributesPacket(); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); attributesPacket.setRuntimeEntityId(entity.getGeyserId()); attributesPacket.getAttributes().addAll(entity.getAttributes().values()); session.sendUpstreamPacket(attributesPacket); - break; - case JUMP: - entity.setOnGround(false); // Increase block break time while jumping - break; - case MISSED_SWING: + } + case JUMP -> entity.setOnGround(false); // Increase block break time while jumping + case MISSED_SWING -> { // Java edition sends a cooldown when hitting air. // Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this. CooldownUtils.sendCooldown(session); @@ -294,18 +303,18 @@ public class BedrockActionTranslator extends PacketTranslator { // Since 1.20.30 if (session.isCanFly()) { if (session.getGameMode() == GameMode.SPECTATOR) { - // should already be flying + // should already be flying session.sendAdventureSettings(); break; } if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) { - // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling - // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE + // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling + // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE session.sendAdventureSettings(); break; } @@ -313,9 +322,9 @@ public class BedrockActionTranslator extends PacketTranslator { session.setFlying(false); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); - break; - case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK: // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70 + } + case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70 BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector); - + if (state.getValue(Properties.HAS_BOOK, false)) { session.setDroppingLecternBook(true); ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket( - vector, - Direction.DOWN, - Hand.MAIN_HAND, - 0, 0, 0, - false, - session.getWorldCache().nextPredictionSequence()); + vector, + Direction.DOWN, + Hand.MAIN_HAND, + 0, 0, 0, + false, + session.getWorldCache().nextPredictionSequence()); session.sendDownstreamGamePacket(blockPacket); break; } @@ -349,10 +358,30 @@ public class BedrockActionTranslator extends PacketTranslator levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP); + case DOWN -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_DOWN); + case NORTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_NORTH); + case EAST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_EAST); + case SOUTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_SOUTH); + case WEST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_WEST); + } + levelEventPacket.setPosition(position.toFloat()); + levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId()); + session.sendUpstreamPacket(levelEventPacket); + } + + private void sendPlayerGlideToggle(GeyserSession session, Entity entity) { + ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); + session.sendDownstreamGamePacket(glidePacket); + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationPacketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationPacketTranslator.java new file mode 100644 index 000000000..8ade4a1f0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationPacketTranslator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION 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 org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.mcprotocollib.protocol.packet.configuration.clientbound.ClientboundFinishConfigurationPacket; + +@Translator(packet = ClientboundFinishConfigurationPacket.class) +public class JavaFinishConfigurationPacketTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundFinishConfigurationPacket packet) { + // Clear the player list, as on Java the player list is cleared after transitioning from config to play phase + PlayerListPacket playerListPacket = new PlayerListPacket(); + playerListPacket.setAction(PlayerListPacket.Action.REMOVE); + for (PlayerEntity otherEntity : session.getEntityCache().getAllPlayerEntities()) { + playerListPacket.getEntries().add(new PlayerListPacket.Entry(otherEntity.getTabListUuid())); + } + session.sendUpstreamPacket(playerListPacket); + session.getEntityCache().removeAllPlayerEntities(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 6988d6cc8..6c065a392 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -79,25 +79,6 @@ public class JavaLoginTranslator extends PacketTranslator { craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId())); } + case CRAFTING_SPECIAL_FIREWORK_ROCKET -> { + craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000002"), context.getAndIncrementNetId())); + } default -> { List recipes = Registries.RECIPES.get(recipe.getType()); if (recipes != null) { @@ -427,7 +448,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator { BlockState state = session.getGeyser().getWorldManager().blockAt(session, position); - boolean sticky = state.is(Blocks.STICKY_PISTON); + boolean sticky = isSticky(state); boolean extended = action != PistonValueType.PUSHING; return new PistonBlockEntity(session, pos, direction, sticky, extended); }); @@ -149,4 +157,8 @@ public class JavaBlockEventTranslator extends PacketTranslator