From cb8858fc423bdba64b8d6ee2b08b7870261e9383 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:44:15 -0400 Subject: [PATCH 01/17] Don't always store cert/client data used for skin uploaded This takes up a decent 30K of memory that we don't use after the skin is uploaded. The GameProfileTranslator cannot be run more than once per session. --- .../org/geysermc/geyser/session/GeyserSession.java | 6 ++++++ .../org/geysermc/geyser/session/auth/AuthData.java | 13 +------------ .../geyser/session/auth/BedrockClientData.java | 6 ++++++ .../protocol/java/JavaGameProfileTranslator.java | 8 +++++++- .../geysermc/geyser/util/LoginEncryptionUtils.java | 6 ++++-- 5 files changed, 24 insertions(+), 15 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 1c25c2281..aeb8e9970 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.session; +import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; @@ -142,6 +143,11 @@ public class GeyserSession implements GeyserConnection, CommandSender { private AuthData authData; @Setter private BedrockClientData clientData; + /** + * Used for Floodgate skin uploading + */ + @Setter + private JsonNode certChainData; /* Setter for GeyserConnect */ @Setter diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java index 802ee3ca0..99b7ae3af 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java @@ -25,18 +25,7 @@ package org.geysermc.geyser.session.auth; -import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.geyser.GeyserImpl; - import java.util.UUID; -public record AuthData(String name, UUID uuid, String xuid, - JsonNode certChainData, String clientData) { - - public void upload(GeyserImpl geyser) { - // we can't upload the skin in LoginEncryptionUtil since the global server would return - // the skin too fast, that's why we upload it after we know for sure that the target server - // is ready to handle the result of the global server - geyser.getSkinUploader().uploadSkin(certChainData, clientData); - } +public record AuthData(String name, UUID uuid, String xuid) { } diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index b3601f6c3..07dd38491 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.session.auth; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import lombok.Setter; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.UiProfile; @@ -107,6 +109,10 @@ public final class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; + @JsonIgnore + @Setter + private String originalString = null; + public DeviceOs getDeviceOs() { return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java index c35261e78..199d29e30 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java @@ -57,7 +57,13 @@ public class JavaGameProfileTranslator extends PacketTranslator Date: Tue, 12 Apr 2022 19:04:09 -0400 Subject: [PATCH 02/17] Make all moon phases visible The fix to prevent integer overflows also prevented moon phases from being visible until now. Fixes #2927 --- .../protocol/java/level/JavaSetTimeTranslator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java index 581433a5f..781ae4ec7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java @@ -42,7 +42,10 @@ public class JavaSetTimeTranslator extends PacketTranslator= 0) { // Client thinks there is no daylight cycle but there is From 0803c5d9af291224fcf3f8a3cc7f458b2c2cc957 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 12 Apr 2022 19:42:41 -0400 Subject: [PATCH 03/17] SetTimeTranslator: cast from long on the entire modulus This should fix some inaccuracies with time on older worlds. --- .../translator/protocol/java/level/JavaSetTimeTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java index 781ae4ec7..bc4e8c1ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSetTimeTranslator.java @@ -45,7 +45,7 @@ public class JavaSetTimeTranslator extends PacketTranslator= 0) { // Client thinks there is no daylight cycle but there is From cf8114543e38032e02f27d672f8f98c919e2c112 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 17 Apr 2022 19:53:06 -0400 Subject: [PATCH 04/17] Bump version; drop 1.17.40; support 1.18.30 --- README.md | 2 +- core/pom.xml | 4 +- .../entity/type/player/PlayerEntity.java | 2 + .../entity/type/player/SkullPlayerEntity.java | 2 + .../geyser/level/BedrockDimension.java | 41 + .../level/block/BlockPositionIterator.java | 2 - .../geyser/network/MinecraftProtocol.java | 13 +- .../geyser/network/UpstreamPacketHandler.java | 15 +- .../populator/BlockRegistryPopulator.java | 72 +- .../populator/ItemRegistryPopulator.java | 30 +- .../geyser/session/GeyserSession.java | 7 +- .../geyser/session/cache/ChunkCache.java | 5 +- .../item/nbt/EnchantmentTranslator.java | 2 +- .../player/BedrockMovePlayerTranslator.java | 13 +- .../JavaLevelChunkWithLightTranslator.java | 39 +- .../org/geysermc/geyser/util/ChunkUtils.java | 29 +- .../geysermc/geyser/util/DimensionUtils.java | 2 +- ...e.1_17_40.nbt => block_palette.1_18_0.nbt} | Bin .../bedrock/block_palette.1_18_30.nbt | Bin 0 -> 45536 bytes ...17_40.json => creative_items.1_18_30.json} | 2529 +++++++++-------- ....json => runtime_item_states.1_18_30.json} | 264 +- 21 files changed, 1614 insertions(+), 1459 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java rename core/src/main/resources/bedrock/{block_palette.1_17_40.nbt => block_palette.1_18_0.nbt} (100%) create mode 100644 core/src/main/resources/bedrock/block_palette.1_18_30.nbt rename core/src/main/resources/bedrock/{creative_items.1_17_40.json => creative_items.1_18_30.json} (84%) rename core/src/main/resources/bedrock/{runtime_item_states.1_17_40.json => runtime_item_states.1_18_30.json} (96%) diff --git a/README.md b/README.md index 23bde93d2..bbb9532a5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.30 and Minecraft Java 1.18.2. ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/pom.xml b/core/pom.xml index d40b7dcc4..7dc64e08b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -120,8 +120,8 @@ com.github.CloudburstMC.Protocol - bedrock-v486 - 0cd24c0 + bedrock-v503 + 29ecd7a compile 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 58f04a756..0d6c0dac1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -37,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -126,6 +127,7 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); 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 ce1615816..f1a447b57 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -27,6 +27,7 @@ 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; @@ -84,6 +85,7 @@ public class SkullPlayerEntity extends PlayerEntity { addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); addPlayerPacket.setDeviceId(""); addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setGameType(GameType.SURVIVAL); addPlayerPacket.getMetadata().putFlags(flags); dirtyMetadata.apply(addPlayerPacket.getMetadata()); diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java new file mode 100644 index 000000000..78c6b2c6a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level; + +/** + * A data structure to represent what Bedrock believes are the height requirements for a specific dimension. + * As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash + * the client. + * + * @param minY The minimum height Bedrock Edition will accept. + * @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. + * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. + */ +public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) { + public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); + public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); + public static BedrockDimension THE_END = new BedrockDimension(0, 256, true); +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java index 425c78f18..d22150ccf 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java @@ -26,8 +26,6 @@ package org.geysermc.geyser.level.block; import com.nukkitx.network.util.Preconditions; -import lombok.Getter; - public class BlockPositionIterator { private final int minX; diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java index 7ab381375..0f5782f86 100644 --- a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -28,11 +28,14 @@ package org.geysermc.geyser.network; import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; /** * Contains information about the supported protocols in Geyser. @@ -42,7 +45,7 @@ public final class MinecraftProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v486.V486_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v503.V503_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -55,11 +58,11 @@ public final class MinecraftProtocol { private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v486.V486_CODEC.toBuilder() .minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 5ded35259..5ae6fbca9 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -30,18 +30,18 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.data.ExperimentData; import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.PendingMicrosoftAuthentication; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.pack.ResourcePackManifest; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.PendingMicrosoftAuthentication; +import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.util.*; +import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MathUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -165,11 +165,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } - if (session.getUpstream().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - // Allow extended world height in the overworld to work for pre-1.18 clients - stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - session.sendUpstreamPacket(stackPacket); break; 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 8238bcea1..d8aa6a456 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -28,8 +28,9 @@ package org.geysermc.geyser.registry.populator; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import com.nukkitx.nbt.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -60,35 +61,50 @@ public class BlockRegistryPopulator { private static final ImmutableMap, BiFunction> BLOCK_MAPPERS; private static final BiFunction EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null; + private static final BiFunction V486_MAPPER = (bedrockIdentifier, statesBuilder) -> { + statesBuilder.remove("no_drop_bit"); // Used in skulls + if (bedrockIdentifier.equals("minecraft:glow_lichen")) { + // Moved around north, south, west + int bits = (int) statesBuilder.get("multi_face_direction_bits"); + boolean north = (bits & (1 << 2)) != 0; + boolean south = (bits & (1 << 3)) != 0; + boolean west = (bits & (1 << 4)) != 0; + if (north) { + bits |= 1 << 4; + } else { + bits &= ~(1 << 4); + } + if (south) { + bits |= 1 << 2; + } else { + bits &= ~(1 << 2); + } + if (west) { + bits |= 1 << 3; + } else { + bits &= ~(1 << 3); + } + statesBuilder.put("multi_face_direction_bits", bits); + } + return null; + }; + static { ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() - .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER) - .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { - statesBuilder.remove("no_drop_bit"); // Used in skulls - if (bedrockIdentifier.equals("minecraft:glow_lichen")) { - // Moved around north, south, west - int bits = (int) statesBuilder.get("multi_face_direction_bits"); - boolean north = (bits & (1 << 2)) != 0; - boolean south = (bits & (1 << 3)) != 0; - boolean west = (bits & (1 << 4)) != 0; - if (north) { - bits |= 1 << 4; - } else { - bits &= ~(1 << 4); - } - if (south) { - bits |= 1 << 2; - } else { - bits &= ~(1 << 2); - } - if (west) { - bits |= 1 << 3; - } else { - bits &= ~(1 << 3); - } - statesBuilder.put("multi_face_direction_bits", bits); - } - return null; + .put(ObjectIntPair.of("1_18_0", Bedrock_v475.V475_CODEC.getProtocolVersion()), EMPTY_MAPPER) + .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), V486_MAPPER) + .put(ObjectIntPair.of("1_18_30", Bedrock_v503.V503_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> { + // Apply these fixes too + V486_MAPPER.apply(bedrockIdentifier, statesBuilder); + return switch (bedrockIdentifier) { + case "minecraft:pistonArmCollision" -> "minecraft:piston_arm_collision"; + case "minecraft:stickyPistonArmCollision" -> "minecraft:sticky_piston_arm_collision"; + case "minecraft:movingBlock" -> "minecraft:moving_block"; + case "minecraft:tripWire" -> "minecraft:trip_wire"; + case "minecraft:seaLantern" -> "minecraft:sea_lantern"; + case "minecraft:concretePowder" -> "minecraft:concrete_powder"; + default -> null; + }; }); BLOCK_MAPPERS = stateMapperBuilder.build(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 0e12669e3..37b6c49f4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -35,10 +35,12 @@ 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.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v486.Bedrock_v486; -import it.unimi.dsi.fastutil.ints.*; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.*; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; @@ -58,19 +60,16 @@ import java.util.*; * Populates the item registries. */ public class ItemRegistryPopulator { - private static final Map PALETTE_VERSIONS; - - static { - PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); - PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); - PALETTE_VERSIONS.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); - } private record PaletteVersion(int protocolVersion, Map additionalTranslatedItems) { } public static void populate() { + Map paletteVersions = new Object2ObjectOpenHashMap<>(); + paletteVersions.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap())); + paletteVersions.put("1_18_30", new PaletteVersion(Bedrock_v503.V503_CODEC.getProtocolVersion(), Collections.emptyMap())); + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); TypeReference> mappingItemsType = new TypeReference<>() { }; @@ -88,7 +87,7 @@ public class ItemRegistryPopulator { Int2IntMap dyeColors = new FixedInt2IntMap(); /* Load item palette */ - for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { + for (Map.Entry palette : paletteVersions.entrySet()) { TypeReference> paletteEntriesType = new TypeReference<>() {}; // Used to get the Bedrock namespaced ID (in instances where there are small differences) @@ -232,12 +231,15 @@ public class ItemRegistryPopulator { } String bedrockIdentifier; - if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - bedrockIdentifier = "minecraft:music_disc_pigstep"; - } else if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { + if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) { bedrockIdentifier = "minecraft:banner_pattern"; } else { bedrockIdentifier = mappingItem.getBedrockIdentifier(); + if (palette.getValue().protocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion()) { + if (bedrockIdentifier.equals("minecraft:sealantern")) { + bedrockIdentifier = "minecraft:sea_lantern"; + } + } } if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index aeb8e9970..f35378af3 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,6 @@ import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.ints.*; @@ -1388,7 +1387,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); startGamePacket.setRotation(Vector2f.from(1, 1)); - startGamePacket.setSeed(-1); + startGamePacket.setSeed(-1L); startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); @@ -1437,10 +1436,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { settings.setServerAuthoritativeBlockBreaking(false); startGamePacket.setPlayerMovementSettings(settings); - if (upstream.getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { - startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); - } - upstream.sendPacket(startGamePacket); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index feb1cf3a8..91d6b33d6 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -33,6 +33,7 @@ import lombok.Setter; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.chunk.GeyserChunk; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.util.MathUtils; public class ChunkCache { @@ -45,11 +46,11 @@ public class ChunkCache { private int heightY; /** - * Whether the Bedrock client believes they are in a world with a minimum of -64 and maximum of 320 + * Which dimension Bedrock understands themselves to be in. */ @Getter @Setter - private boolean isExtendedHeight = false; + private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD; public ChunkCache(GeyserSession session) { this.cache = !session.getGeyser().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 847a70a27..55d45f67e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -136,7 +136,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { CompoundTag bedrockTag = new CompoundTag(""); bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); // If the tag cannot parse, Java Edition 1.18.2 sets to 0 - bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : 0)); + bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : (short) 0)); return bedrockTag; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java index 2fccbe482..a63c0f334 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -37,14 +37,12 @@ import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { - /* The upper and lower bounds to check for the void floor that only exists in Bedrock. These are the constants for the overworld. */ - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y = -104; - private static final int BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y = BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y + 2; @Override public void translate(GeyserSession session, MovePlayerPacket packet) { @@ -124,11 +122,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y : -40)) { + // The void floor is offset about 40 blocks below the bottom of the world + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int voidFloorLocation = bedrockDimension.minY() - 40; + if (floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation) { // Work around there being a floor at the bottom of the world and teleport the player below it // Moving from below to above the void floor works fine entity.setPosition(entity.getPosition().sub(0, 4f, 0)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index 97b826473..165d90b36 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -43,7 +43,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; @@ -52,26 +52,29 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.entity.type.ItemFrameEntity; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.geyser.level.block.BlockStateValues; -import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; -import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; -import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; import org.geysermc.geyser.level.chunk.BlockStorage; import org.geysermc.geyser.level.chunk.GeyserChunkSection; import org.geysermc.geyser.level.chunk.bitarray.BitArray; import org.geysermc.geyser.level.chunk.bitarray.BitArrayVersion; import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.BedrockDimension; +import org.geysermc.geyser.translator.level.BiomeTranslator; +import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.ChunkUtils; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.*; +import java.util.BitSet; +import java.util.List; +import java.util.Map; import static org.geysermc.geyser.util.ChunkUtils.*; @@ -98,13 +101,13 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4) - 1; + BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); + int maxBedrockSectionY = (bedrockDimension.height() >> 4) - 1; int sectionCount; byte[] payload; ByteBuf byteBuf = null; - GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4))]; + GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - (yOffset + (bedrockDimension.minY() >> 4))]; try { NetInput in = new StreamNetInput(new ByteArrayInputStream(packet.getChunkData())); @@ -113,7 +116,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 4)); + int bedrockSectionY = sectionY + (yOffset - (bedrockDimension.minY() >> 4)); if (bedrockSectionY < 0 || maxBedrockSectionY < bedrockSectionY) { // Ignore this chunk section since it goes outside the bounds accepted by the Bedrock client continue; @@ -309,11 +312,11 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator= Bedrock_v475.V475_CODEC.getProtocolVersion(); - int biomeCount = isNewVersion ? 25 : 32; - int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4; + // As of 1.18.0, Bedrock hardcodes to always read 25 biome sections + // As of 1.18.30, the hardcode may now be tied to the dimension definition + boolean isNewVersion = session.getUpstream().getProtocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion(); + int biomeCount = isNewVersion ? bedrockDimension.height() >> 4 : 25; + int dimensionOffset = bedrockDimension.minY() >> 4; for (int i = 0; i < biomeCount; i++) { int biomeYOffset = dimensionOffset + i; if (biomeYOffset < yOffset) { diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index 7fdf12ec9..445ffb882 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -46,23 +46,13 @@ import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity; import static org.geysermc.geyser.level.block.BlockStateValues.JAVA_AIR_ID; @UtilityClass public class ChunkUtils { - /** - * The minimum height Bedrock Edition will accept. - */ - public static final int MINIMUM_ACCEPTED_HEIGHT = 0; - public static final int MINIMUM_ACCEPTED_HEIGHT_OVERWORLD = -64; - /** - * The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest. - */ - public static final int MAXIMUM_ACCEPTED_HEIGHT = 256; - public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384; - /** * An empty subchunk. */ @@ -249,17 +239,20 @@ public class ChunkUtils { throw new RuntimeException("Maximum Y must be a multiple of 16!"); } - int dimension = DimensionUtils.javaToBedrock(session.getDimension()); - boolean extendedHeight = dimension == 0; - session.getChunkCache().setExtendedHeight(extendedHeight); + BedrockDimension bedrockDimension = switch (session.getDimension()) { + case DimensionUtils.THE_END -> BedrockDimension.THE_END; + case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER; + default -> BedrockDimension.OVERWORLD; + }; + session.getChunkCache().setBedrockDimension(bedrockDimension); // Yell in the console if the world height is too height in the current scenario // The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled - if (minY < (extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) - || maxY > (extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT)) { + // (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ ) + if (minY < bedrockDimension.minY() || (bedrockDimension.doUpperHeightWarn() && maxY > bedrockDimension.height())) { session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds", - extendedHeight ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT, - extendedHeight ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT, + String.valueOf(bedrockDimension.minY()), + String.valueOf(bedrockDimension.height()), session.getDimension())); } diff --git a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java index 5af5e2c2b..f1aca63e8 100644 --- a/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java @@ -109,7 +109,7 @@ public class DimensionUtils { // we check if the player is entering the nether and apply the nether fog to fake the fact that the client // thinks they are in the end dimension. if (BEDROCK_NETHER_ID == 2) { - if (bedrockDimension == BEDROCK_NETHER_ID) { + if (NETHER.equals(javaDimension)) { session.sendFog("minecraft:fog_hell"); } else if (previousDimension == BEDROCK_NETHER_ID) { session.removeFog("minecraft:fog_hell"); diff --git a/core/src/main/resources/bedrock/block_palette.1_17_40.nbt b/core/src/main/resources/bedrock/block_palette.1_18_0.nbt similarity index 100% rename from core/src/main/resources/bedrock/block_palette.1_17_40.nbt rename to core/src/main/resources/bedrock/block_palette.1_18_0.nbt diff --git a/core/src/main/resources/bedrock/block_palette.1_18_30.nbt b/core/src/main/resources/bedrock/block_palette.1_18_30.nbt new file mode 100644 index 0000000000000000000000000000000000000000..bf7690970f792f71b62d7b7f0c9a2f80a3292a21 GIT binary patch literal 45536 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 Date: Sun, 17 Apr 2022 20:10:16 -0400 Subject: [PATCH 05/17] Actually bump to 2.0.3-SNAPSHOT --- ap/pom.xml | 4 ++-- api/base/pom.xml | 2 +- api/geyser/pom.xml | 4 ++-- api/pom.xml | 2 +- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 4 ++-- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- core/pom.xml | 8 ++++---- pom.xml | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ap/pom.xml b/ap/pom.xml index 75f98275c..0644044a1 100644 --- a/ap/pom.xml +++ b/ap/pom.xml @@ -6,9 +6,9 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT \ No newline at end of file diff --git a/api/base/pom.xml b/api/base/pom.xml index 37e97ef7e..1d051eaa3 100644 --- a/api/base/pom.xml +++ b/api/base/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 9255bc579..2a933a2e0 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -5,7 +5,7 @@ org.geysermc api-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT 4.0.0 @@ -26,7 +26,7 @@ org.geysermc base-api - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/api/pom.xml b/api/pom.xml index bae0da039..5d078fba5 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT api-parent diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index f06a219bb..87e89c1f7 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 381f68bc2..7d6ac8f98 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-parent pom @@ -34,7 +34,7 @@ org.geysermc ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT provided diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 26f9c7083..98052d420 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-spigot @@ -30,7 +30,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 6285c6dbf..0718996ab 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 6babc6933..6fb45d800 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-standalone @@ -18,7 +18,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 1621d6ee6..93fb1441e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc core - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index a563b7aff..b3903f412 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT common diff --git a/core/pom.xml b/core/pom.xml index 7dc64e08b..2b6956c1f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT core @@ -21,19 +21,19 @@ org.geysermc ap - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT provided org.geysermc geyser-api - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile org.geysermc common - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 0599716fe..99524e320 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From af08488d1eab4daf0b24156a95e527b69df4c194 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 18 Apr 2022 21:30:44 -0400 Subject: [PATCH 06/17] Fix message being sent still if a single escape character is sent --- .../protocol/bedrock/BedrockTextTranslator.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 1a6771cc5..91ed5aa2b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -40,11 +40,8 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(GeyserSession session, TextPacket packet) { String message = packet.getMessage(); - if (message.isBlank()) { - // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! - return; - } - + // The order here is important - strip out illegal characters first, then check if it's blank + // (in case the message is blank after removing) if (message.indexOf(ChatColor.ESCAPE) != -1) { // Filter out all escape characters - Java doesn't let you type these StringBuilder builder = new StringBuilder(); @@ -57,6 +54,11 @@ public class BedrockTextTranslator extends PacketTranslator { message = builder.toString(); } + if (message.isBlank()) { + // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! + return; + } + if (MessageTranslator.isTooLong(message, session)) { return; } From 137eb3ece80382460d655929c72c803ac50036ab Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 19 Apr 2022 10:18:50 -0400 Subject: [PATCH 07/17] Replace instances of configs using `generateduuid` for Metrics --- .../configuration/GeyserJacksonConfiguration.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 463350441..03a3617e3 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -35,9 +35,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.text.AsteriskSerializer; -import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; @@ -240,8 +240,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; + @JsonDeserialize(using = MetricsIdDeserializer.class) @JsonProperty("uuid") private String uniqueId = UUID.randomUUID().toString(); + + private static class MetricsIdDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String uuid = p.getValueAsString(); + if ("generateduuid".equals(uuid)) { + // Compensate for configs not copied from the jar + return UUID.randomUUID().toString(); + } + return uuid; + } + } } @JsonProperty("scoreboard-packet-threshold") From b528a1c4f6d2934234c5d33af0c6816b7c7a7c6f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:30:45 -0400 Subject: [PATCH 08/17] Update Protocol to better support older Netty versions --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 2b6956c1f..7316afe0e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -121,7 +121,7 @@ com.github.CloudburstMC.Protocol bedrock-v503 - 29ecd7a + f32c76d compile From e923325246eb2af3323762f6734723cb469a2d31 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:22:02 -0400 Subject: [PATCH 09/17] Fix stonecutters for Bedrock 1.18.30 Also add an option in debug mode to not log pings in the event they're spammy. --- .../recipe/GeyserStonecutterData.java | 35 ++++++++++++++++ .../network/ConnectorServerEventHandler.java | 4 +- .../geyser/session/GeyserSession.java | 3 +- .../inventory/InventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 41 +++++++------------ .../item/nbt/EnchantmentTranslator.java | 2 +- .../java/JavaUpdateRecipesTranslator.java | 14 ++++--- 7 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java new file mode 100644 index 000000000..04a772c31 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.recipe; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; + +/** + * @param buttonId the button that needs to be pressed for Java Edition to accept this item. + * @param output the expected output of this item when cut. + */ +public record GeyserStonecutterData(int buttonId, ItemStack output) { +} diff --git a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java index 892ddcb64..d41871cdb 100644 --- a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -47,6 +47,8 @@ import java.nio.charset.StandardCharsets; import java.util.List; public class ConnectorServerEventHandler implements BedrockServerEventHandler { + private static final boolean PRINT_DEBUG_PINGS = Boolean.parseBoolean(System.getProperty("Geyser.PrintPingsInDebugMode", "true")); + /* The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client */ @@ -88,7 +90,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { - if (geyser.getConfig().isDebugMode()) { + if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index f35378af3..72eaaf0f7 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -98,6 +98,7 @@ import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; +import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; @@ -365,7 +366,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier */ @Setter - private Int2ObjectMap stonecutterRecipes; + private Int2ObjectMap stonecutterRecipes; /** * Whether to work around 1.13's different behavior in villager trading menus. diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index b48709595..6f4ca7ee4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -144,7 +144,7 @@ public abstract class InventoryTranslator { /** * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return rejectRequest(request); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java index ae25a9ffd..e0e2e27bd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java @@ -31,20 +31,14 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; -import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.geyser.inventory.GeyserItemStack; -import org.geysermc.geyser.inventory.Inventory; -import org.geysermc.geyser.inventory.PlayerInventory; -import org.geysermc.geyser.inventory.StonecutterContainer; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.inventory.BedrockContainerSlot; -import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.*; +import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.session.GeyserSession; public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { public StonecutterInventoryTranslator() { @@ -53,31 +47,26 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { - // First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID - return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE; + return action.getType() == StackRequestActionType.CRAFT_RECIPE; } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // TODO: Also surely to change in the future - StackRequestActionData data = request.getActions()[1]; - if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst + CraftRecipeStackRequestActionData data = (CraftRecipeStackRequestActionData) request.getActions()[0]; + + // Look up all possible options of cutting from this ID + GeyserStonecutterData craftingData = session.getStonecutterRecipes().get(data.getRecipeNetworkId()); + if (craftingData == null) { return rejectRequest(request); } StonecutterContainer container = (StonecutterContainer) inventory; - // Get the ID of the item we are cutting - int id = inventory.getItem(0).getJavaId(); - // Look up all possible options of cutting from this ID - IntList results = session.getStonecutterRecipes().get(id); - if (results == null) { - return rejectRequest(request); - } - - ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0], session.getItemMappings()); - int button = results.indexOf(javaOutput.getId()); + int button = craftingData.buttonId(); // If we've already pressed the button with this item, no need to press it again! if (container.getStonecutterButton() != button) { + ItemStack javaOutput = craftingData.output(); + // Getting the index of the item in the Java stonecutter list ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), button); session.sendDownstreamPacket(packet); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 55d45f67e..cd6d5d6ff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -127,7 +127,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment while NBT item translating: " + javaEnchId.getValue()); return null; } 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 09ddfce4c..1c5a15b0b 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 @@ -45,6 +45,7 @@ import lombok.EqualsAndHashCode; 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.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -167,7 +168,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore // We can get the correct order for button pressing @@ -176,11 +177,13 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator new IntArrayList()); - outputs.add(stoneCuttingData.getResult().getId()); + // Add the net ID as the key and the button required + output for the value + stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput)); } } From 575fe98c0f67e71eef9479eef03b9d89e0b947c2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:39:35 -0400 Subject: [PATCH 10/17] Fix anvils for 1.18.30 Bedrock --- .../geyser/inventory/AnvilContainer.java | 37 +++++++++++++++++++ .../updater/AnvilInventoryUpdater.java | 8 ++-- .../inventory/AnvilInventoryTranslator.java | 27 ++++++++++++++ .../bedrock/BedrockFilterTextTranslator.java | 26 +------------ 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java index a2b7ff9d6..688151a9e 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -26,8 +26,14 @@ package org.geysermc.geyser.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.ItemUtils; + +import javax.annotation.Nullable; /** * Used to determine if rename packets should be sent and stores @@ -48,6 +54,7 @@ public class AnvilContainer extends Container { /** * The new name of the item as received from Bedrock */ + @Nullable private String newName = null; private GeyserItemStack lastInput = GeyserItemStack.EMPTY; @@ -59,6 +66,36 @@ public class AnvilContainer extends Container { super(title, id, size, containerType, playerInventory); } + /** + * @return the name to use instead for renaming. + */ + public String checkForRename(GeyserSession session, String rename) { + String correctRename; + newName = rename; + + String originalName = ItemUtils.getCustomName(getInput().getNbt()); + + String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale()); + String plainNewName = MessageTranslator.convertToPlainText(rename, session.getLocale()); + if (!plainOriginalName.equals(plainNewName)) { + // Strip out formatting since Java Edition does not allow it + correctRename = plainNewName; + // Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName); + session.sendDownstreamPacket(renameItemPacket); + } else { + // Restore formatting for item since we're not renaming + correctRename = MessageTranslator.convertMessageLenient(originalName); + // Java Edition sends the original custom name when not renaming, + // if there isn't a custom name an empty string is sent + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName); + session.sendDownstreamPacket(renameItemPacket); + } + + useJavaLevelCost = false; + return correctRename; + } + public GeyserItemStack getInput() { return getItem(0); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index d6f72b8d0..655d0f215 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -384,19 +384,19 @@ public class AnvilInventoryUpdater extends InventoryUpdater { if (enchantTag.get("id") instanceof StringTag javaEnchId) { JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue()); if (enchantment == null) { - GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + javaEnchId.getValue()); continue; } Tag javaEnchLvl = enchantTag.get("lvl"); - if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) + if (javaEnchLvl == null || !(javaEnchLvl.getValue() instanceof Number number)) continue; // Handle duplicate enchantments if (bedrock) { - enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue()); + enchantments.putIfAbsent(enchantment, number.intValue()); } else { - enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max); + enchantments.mergeInt(enchantment, number.intValue(), Math::max); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java index b09fcb7d4..e56586b14 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java @@ -27,7 +27,12 @@ package org.geysermc.geyser.translator.inventory; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeOptionalStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; @@ -35,12 +40,34 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.BedrockContainerSlot; import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; +import java.util.Objects; + public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE, "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } + @Override + protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_RECIPE_OPTIONAL; + } + + @Override + protected ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Guarded by shouldHandleRequestFirst check + CraftRecipeOptionalStackRequestActionData data = (CraftRecipeOptionalStackRequestActionData) request.getActions()[0]; + AnvilContainer container = (AnvilContainer) inventory; + + // Required as of 1.18.30 - FilterTextPackets no longer appear to be sent + String name = request.getFilterStrings()[data.getFilteredStringIndex()]; + if (!Objects.equals(name, container.getNewName())) { + container.checkForRename(session, name); + } + + return super.translateRequest(session, inventory, request); + } + @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { return switch (slotInfoData.getContainer()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java index 818829e8f..4e729bc59 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java @@ -25,15 +25,12 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; import org.geysermc.geyser.inventory.AnvilContainer; import org.geysermc.geyser.inventory.CartographyContainer; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.ItemUtils; /** * Used to send strings to the server and filter out unwanted words. @@ -50,28 +47,7 @@ public class BedrockFilterTextTranslator extends PacketTranslator Date: Thu, 21 Apr 2022 14:23:02 -0400 Subject: [PATCH 11/17] Update bug_report.yml Request the device that the Bedrock player is experiencing the bug on --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f4a0e21ff..036d838ef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,9 +55,9 @@ body: required: true - type: input attributes: - label: "Minecraft: Bedrock Edition Version" - description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." - placeholder: "For example: 1.16.201" + label: "Minecraft: Bedrock Edition Device/Version" + description: "What version of Minecraft: Bedrock Edition are you using, and what device(s) does the bug occur on? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." + placeholder: "For example: 1.16.201, Nintendo Switch" - type: textarea attributes: label: Additional Context From 05d74ebd3efc89a7e92e27feaf91c81d2ac050e4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 21 Apr 2022 22:23:00 -0400 Subject: [PATCH 12/17] Fix signs for Bedrock 1.18.30 Fixes #2944 --- .../protocol/bedrock/BedrockBlockEntityDataTranslator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index d00914fb1..6da4539cf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundSignUpdatePacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.v503.Bedrock_v503; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -40,6 +41,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator Date: Thu, 21 Apr 2022 22:24:41 -0400 Subject: [PATCH 13/17] oops --- .../protocol/bedrock/BedrockBlockEntityDataTranslator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index 6da4539cf..1f2f29ea5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -41,7 +41,6 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator Date: Fri, 22 Apr 2022 18:06:38 -0400 Subject: [PATCH 14/17] Don't send the SetHealthPacket clientbound Seems like this can cause the client to break in 1.18.30, and we already send the health as an attribute. --- .../java/entity/player/JavaSetHealthTranslator.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java index f21823b07..d989fe964 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.entity.player; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetHealthPacket; import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; @@ -44,11 +43,6 @@ public class JavaSetHealthTranslator extends PacketTranslator Date: Fri, 22 Apr 2022 20:16:22 -0400 Subject: [PATCH 15/17] Make completed advancement color easier to read Resolves #2937 --- .../geysermc/geyser/session/cache/AdvancementsCache.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java index 9d3e4f5aa..0df842d1d 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.Ser import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.level.GeyserAdvancement; import org.geysermc.geyser.text.GeyserLocale; @@ -140,7 +141,7 @@ public class AdvancementsCache { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); - builder.button((color ? "ยง6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); + builder.button((color ? ChatColor.DARK_GREEN : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } @@ -266,10 +267,9 @@ public class AdvancementsCache { } public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { - String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { - return base + "5"; + return ChatColor.DARK_PURPLE; } - return base + "a"; // Used for types TASK and GOAL + return ChatColor.GREEN; // Used for types TASK and GOAL } } From 4cb18ebf5b483de1e4ac4a211c40f07659c44fd9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 23 Apr 2022 14:14:09 -0400 Subject: [PATCH 16/17] Use stable version for maven-shade-plugin --- bootstrap/bungeecord/pom.xml | 2 +- bootstrap/spigot/pom.xml | 2 +- bootstrap/sponge/pom.xml | 2 +- bootstrap/standalone/pom.xml | 2 +- bootstrap/velocity/pom.xml | 2 +- pom.xml | 13 ------------- 6 files changed, 5 insertions(+), 18 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 87e89c1f7..b2c661447 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 98052d420..e9e7687f4 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 0718996ab..8eba4d73d 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 6fb45d800..8ee94b793 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -93,7 +93,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 com.github.edwgiz diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 93fb1441e..36882a19e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0-SNAPSHOT + 3.3.0 package diff --git a/pom.xml b/pom.xml index 99524e320..f4930959d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,20 +40,7 @@ core - - - - apache.snapshots - https://repository.apache.org/snapshots/ - - - - - - apache.snapshots - https://repository.apache.org/snapshots/ - jitpack.io https://jitpack.io From 2f54bf0e14b50d4887566dcb30e17629e0f0ec4d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 23 Apr 2022 20:57:32 +0200 Subject: [PATCH 17/17] Rotation fixes (#2396) * Should fix some rotation issues * Some more changes * Small changes * Fixed merge conflicts and updated other classes that changed * Added translation for the LookAt packet --- .../entity/type/AbstractArrowEntity.java | 6 +- .../geyser/entity/type/BoatEntity.java | 4 +- .../geysermc/geyser/entity/type/Entity.java | 15 ++-- .../geyser/entity/type/FireballEntity.java | 2 +- .../geyser/entity/type/FishingHookEntity.java | 4 +- .../geyser/entity/type/ItemEntity.java | 8 +-- .../geyser/entity/type/MinecartEntity.java | 2 +- .../geyser/entity/type/ThrowableEntity.java | 14 ++-- .../entity/type/living/ArmorStandEntity.java | 31 ++++----- .../entity/type/living/SquidEntity.java | 2 +- .../living/monster/EnderDragonEntity.java | 10 +-- .../entity/type/player/PlayerEntity.java | 17 +++-- .../player/BedrockMovePlayerTranslator.java | 10 +-- .../player/JavaPlayerLookAtTranslator.java | 68 +++++++++++++++++++ .../player/JavaPlayerPositionTranslator.java | 10 ++- .../org/geysermc/geyser/util/MathUtils.java | 19 +++++- 16 files changed, 154 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java index db0cfc738..963e0b70a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java @@ -70,8 +70,8 @@ public class AbstractArrowEntity extends Entity { super.setMotion(motion); double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); - this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())); - this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)); - this.headYaw = yaw; + setYaw((float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()))); + setPitch((float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed))); + setHeadYaw(getYaw()); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index 6ce490bc2..9fd96f46b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -81,8 +81,8 @@ public class BoatEntity extends Entity { public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { // We don't include the rotation (y) as it causes the boat to appear sideways setPosition(position.add(0d, this.definition.offset(), 0d)); - this.yaw = yaw + 90; - this.headYaw = yaw + 90; + setYaw(yaw + 90); + setHeadYaw(yaw + 90); setOnGround(isOnGround); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); 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 270f69ee0..4dc3a437a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -204,7 +204,7 @@ public class Entity { } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround); + moveRelative(relX, relY, relZ, yaw, pitch, getHeadYaw(), isOnGround); } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { @@ -225,7 +225,7 @@ public class Entity { } public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { - moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported); + moveAbsolute(position, yaw, pitch, getHeadYaw(), isOnGround, teleported); } public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { @@ -254,7 +254,8 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(position, yaw, pitch, isOnGround, false); + // teleport will always set the headYaw to yaw + moveAbsolute(position, yaw, pitch, yaw, isOnGround, false); } /** @@ -262,7 +263,7 @@ public class Entity { * @param headYaw The new head rotation of the entity. */ public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); } /** @@ -275,7 +276,7 @@ public class Entity { * @param isOnGround Whether the entity is currently on the ground. */ public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround); + moveRelative(moveX, moveY, moveZ, yaw, pitch, getHeadYaw(), isOnGround); } /** @@ -436,12 +437,12 @@ public class Entity { } /** - * x = Pitch, y = HeadYaw, z = Yaw + * x = Pitch, y = Yaw, z = HeadYaw * * @return the bedrock rotation */ public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, headYaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getHeadYaw()); } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java index 744ddf4a6..135f58906 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -72,6 +72,6 @@ public class FireballEntity extends ThrowableEntity { @Override public void tick() { - moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false); + moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java index 2f5590c37..52ad82370 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -152,7 +152,7 @@ public class FishingHookEntity extends ThrowableEntity { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag); @@ -160,7 +160,7 @@ public class FishingHookEntity extends ThrowableEntity { @Override protected float getGravity() { - if (!isInWater() && !onGround) { + if (!isInWater() && !isOnGround()) { return 0.03f; } return 0; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index 79ffe68ef..f36a7c732 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -76,10 +76,10 @@ public class ItemEntity extends ThrowableEntity { if (isInWater()) { return; } - if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { + if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag, 0.98f, drag); } @@ -124,7 +124,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getGravity() { - if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) { + if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) { // Gravity can change if the item is in water/lava, but // the server calculates the motion & position for us return 0.04f; @@ -134,7 +134,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getDrag() { - if (onGround) { + if (isOnGround()) { Vector3i groundBlockPos = position.toInt().down(1); int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos); return BlockStateValues.getSlipperiness(blockState) * 0.98f; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index a427d6a43..6f722864b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -66,7 +66,7 @@ public class MinecartEntity extends Entity { @Override public Vector3f getBedrockRotation() { // Note: minecart rotation on rails does not care about the actual rotation value - return Vector3f.from(0, yaw, 0); + return Vector3f.from(0, getYaw(), 0); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index 87e3be405..ad8b60bdb 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -56,7 +56,7 @@ public class ThrowableEntity extends Entity implements Tickable { */ @Override public void tick() { - moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); float gravity = getGravity(); motion = motion.mul(drag).down(gravity); @@ -89,20 +89,20 @@ public class ThrowableEntity extends Entity implements Tickable { } setPosition(position); - if (this.yaw != yaw) { + if (getYaw() != yaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); moveEntityDeltaPacket.setYaw(yaw); - this.yaw = yaw; + setYaw(yaw); } - if (this.pitch != pitch) { + if (getPitch() != pitch) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); moveEntityDeltaPacket.setPitch(pitch); - this.pitch = pitch; + setPitch(pitch); } - if (this.headYaw != headYaw) { + if (getHeadYaw() != headYaw) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); moveEntityDeltaPacket.setHeadYaw(headYaw); - this.headYaw = headYaw; + setHeadYaw(headYaw); } if (!moveEntityDeltaPacket.getFlags().isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 9c7e6d107..18076763e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -42,6 +42,7 @@ import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; +import org.geysermc.geyser.util.MathUtils; import java.util.Optional; import java.util.UUID; @@ -87,8 +88,6 @@ public class ArmorStandEntity extends LivingEntity { @Override public void spawnEntity() { - this.pitch = yaw; - this.headYaw = yaw; super.spawnEntity(); } @@ -205,9 +204,9 @@ public class ArmorStandEntity extends LivingEntity { // Indicate that rotation should be checked setFlag(EntityFlag.BRIBED, true); - int rotationX = getRotation(rotation.getPitch()); - int rotationY = getRotation(rotation.getYaw()); - int rotationZ = getRotation(rotation.getRoll()); + int rotationX = MathUtils.wrapDegreesToInt(rotation.getPitch()); + int rotationY = MathUtils.wrapDegreesToInt(rotation.getYaw()); + int rotationZ = MathUtils.wrapDegreesToInt(rotation.getRoll()); // The top bit acts like binary and determines if each rotation goes above 100 // We don't do this for the negative values out of concerns of the number being too big int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); @@ -319,7 +318,7 @@ public class ArmorStandEntity extends LivingEntity { // Create the second entity. It doesn't need to worry about the items, but it does need to worry about // the metadata as it will hold the name tag. secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, - EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw); + EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw()); secondEntity.primaryEntity = false; if (!this.positionRequiresOffset) { // Ensure the offset is applied for the 0 scale @@ -375,17 +374,6 @@ public class ArmorStandEntity extends LivingEntity { } } - private int getRotation(float rotation) { - rotation = rotation % 360f; - if (rotation < -180f) { - rotation += 360f; - } else if (rotation >= 180f) { - // 181 -> -179 - rotation = -(180 - (rotation - 180)); - } - return (int) rotation; - } - /** * If this armor stand is not a marker, set its bounding box size and scale. */ @@ -439,9 +427,14 @@ public class ArmorStandEntity extends LivingEntity { MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw)); - moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround()); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); } + + @Override + public Vector3f getBedrockRotation() { + return Vector3f.from(getYaw(), getYaw(), getYaw()); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index c81cf68de..552f6a46c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -117,7 +117,7 @@ public class SquidEntity extends WaterEntity implements Tickable { @Override public Vector3f getBedrockRotation() { - return Vector3f.from(pitch, yaw, yaw); + return Vector3f.from(getPitch(), getYaw(), getYaw()); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index 0069bfb5b..99ab1a55c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -130,7 +130,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { for (int i = 0; i < segmentHistory.length; i++) { segmentHistory[i] = new Segment(); - segmentHistory[i].yaw = headYaw; + segmentHistory[i].yaw = getHeadYaw(); segmentHistory[i].y = position.getY(); } } @@ -168,7 +168,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { * Updates the positions of the Ender Dragon's multiple bounding boxes */ private void updateBoundingBoxes() { - Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw); + Vector3f facingDir = Vector3f.createDirectionDeg(0, getHeadYaw()); Segment baseSegment = getSegment(5); // Used to angle the head, neck, and tail when the dragon flies up/down float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); @@ -187,7 +187,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); - Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f); + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - getHeadYaw()).mul(4.5f).up(2f); rightWing.setPosition(wingPos); leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally @@ -196,7 +196,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { float distance = (i + 1) * 2f; // Curls the tail when the dragon turns Segment targetSegment = getSegment(12 + 2 * i); - float angle = headYaw + targetSegment.yaw - baseSegment.yaw; + float angle = getHeadYaw() + targetSegment.yaw - baseSegment.yaw; float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); @@ -306,7 +306,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { */ private void pushSegment() { latestSegment = (latestSegment + 1) % segmentHistory.length; - segmentHistory[latestSegment].yaw = headYaw; + segmentHistory[latestSegment].yaw = getHeadYaw(); segmentHistory[latestSegment].y = position.getY(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 0d6c0dac1..5c0b18838 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -213,7 +213,7 @@ public class PlayerEntity extends LivingEntity { @Override public void updateHeadLookRotation(float headYaw) { - moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround); + moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround()); MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(position); @@ -233,9 +233,11 @@ public class PlayerEntity extends LivingEntity { } } - @Override - public void updateRotation(float yaw, float pitch, boolean isOnGround) { - super.updateRotation(yaw, pitch, isOnGround); + public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) { + // the method below is called by super.updateRotation(yaw, pitch, isOnGround). + // but we have to be able to set the headYaw, so we call the method below directly. + super.moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround); + // Both packets need to be sent or else player head rotation isn't correctly updated MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); @@ -252,6 +254,11 @@ public class PlayerEntity extends LivingEntity { } } + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + updateRotation(yaw, pitch, getHeadYaw(), isOnGround); + } + @Override public void setPosition(Vector3f position) { super.setPosition(position.add(0, definition.offset(), 0)); @@ -300,7 +307,7 @@ public class PlayerEntity extends LivingEntity { } // The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT? ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), - null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw); + null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw()); parrot.spawnEntity(); parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); // Different position whether the parrot is left or right 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 a63c0f334..8732b7909 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 @@ -81,8 +81,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + var targetPosition = targetPosition(session, packet); + var selfPosition = session.getPlayerEntity().getPosition(); + + var xDelta = targetPosition.getX() - selfPosition.getX(); + var yDelta = targetPosition.getY() - selfPosition.getY(); + var zDelta = targetPosition.getZ() - selfPosition.getZ(); + var sqrt = Math.sqrt(xDelta * xDelta + zDelta * zDelta); + + var yaw = MathUtils.wrapDegrees(-Math.toDegrees(Math.atan2(yDelta, sqrt))); + var pitch = MathUtils.wrapDegrees(Math.toDegrees(Math.atan2(zDelta, xDelta)) - 90.0); + + var self = session.getPlayerEntity(); + // headYaw is also set to yaw in this packet + self.updateRotation(yaw, pitch, yaw, self.isOnGround()); + } + + public Vector3f targetPosition(GeyserSession session, ClientboundPlayerLookAtPacket packet) { + if (packet.getTargetEntityOrigin() != null) { + var entityId = packet.getTargetEntityId(); + var target = session.getEntityCache().getEntityByJavaId(entityId); + if (target != null) { + return switch (packet.getTargetEntityOrigin()) { + case FEET -> target.getPosition(); + case EYES -> target.getPosition().add(0, target.getBoundingBoxHeight(), 0); + }; + } + } + return Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 97487ea6a..f5d21ecc9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -74,7 +74,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator= 180.0f) { + degrees -= 360.0f; + } + return degrees; + } + + public static float wrapDegrees(double degrees) { + return wrapDegrees((float) degrees); + } + + public static int wrapDegreesToInt(float degrees) { + return (int) wrapDegrees(degrees); + } + /** * Round the given float to the next whole number *