diff --git a/README.md b/README.md index bbb9532a5..3e247f4b5 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.18.0 - 1.18.30 and Minecraft Java 1.18.2. +### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.31 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/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 4c2f5d651..9ff4c9ebc 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -66,6 +66,8 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private final int cpuCount; + private final Locale systemLocale; + private final String systemEncoding; private Properties gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; @@ -81,6 +83,8 @@ public class DumpInfo { this.versionInfo = new VersionInfo(); this.cpuCount = Runtime.getRuntime().availableProcessors(); + this.systemLocale = Locale.getDefault(); + this.systemEncoding = System.getProperty("file.encoding"); try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { this.gitInfo = new Properties(); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java index 48d0e80e0..a9b3ffedc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -51,6 +51,7 @@ public final class BlockStateValues { private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet(); private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap(); + private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet(); private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); private static final IntSet STICKY_PISTONS = new IntOpenHashSet(); @@ -176,7 +177,7 @@ public final class BlockStateValues { return; } - if (javaId.startsWith("minecraft:water")) { + if (javaId.startsWith("minecraft:water") && !javaId.contains("cauldron")) { String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); int level = Integer.parseInt(strLevel); WATER_LEVEL.put(javaBlockState, level); @@ -189,6 +190,11 @@ public final class BlockStateValues { if (direction.isHorizontal()) { HORIZONTAL_FACING_JIGSAWS.add(javaBlockState); } + return; + } + + if (javaId.contains("_cauldron") && !javaId.contains("water_")) { + NON_WATER_CAULDRONS.add(javaBlockState); } } @@ -214,6 +220,15 @@ public final class BlockStateValues { return BED_COLORS.getOrDefault(state, (byte) -1); } + /** + * Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues. + * + * @return if this Java block state is a non-empty non-water cauldron + */ + public static boolean isCauldron(int state) { + return NON_WATER_CAULDRONS.contains(state); + } + /** * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags * in Bedrock need the conditional information. diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java index 1f28a0264..cb9be9459 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java +++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java @@ -62,7 +62,9 @@ public final class GameProtocol { 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); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + .minecraftVersion("1.18.30/1.18.31") + .build()); } /** 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 d8aa6a456..412d7d779 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -132,7 +132,6 @@ public class BlockRegistryPopulator { } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map javaIdentifierToBedrockTag = new Object2ObjectOpenHashMap<>(blocksTag.size()); // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // as we no longer send a block palette Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); @@ -202,10 +201,6 @@ public class BlockRegistryPopulator { flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); } - if (!cleanJavaIdentifier.equals(entry.getValue().get("bedrock_identifier").asText())) { - javaIdentifierToBedrockTag.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); - } - javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId; } @@ -240,7 +235,6 @@ public class BlockRegistryPopulator { BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) .javaToBedrockBlocks(javaToBedrockBlocks) - .javaIdentifierToBedrockTag(javaIdentifierToBedrockTag) .itemFrames(itemFrames) .flowerPotBlocks(flowerPotBlocks) .jigsawStateIds(jigsawStateIds) diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java index a105682a6..41318ee64 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -47,12 +47,6 @@ public class BlockMappings { NbtList bedrockBlockStates; - /** - * Contains a map of Java blocks to their respective Bedrock block tag, if the Java identifier is different from Bedrock. - * Required to fix villager trades with these blocks. - */ - Map javaIdentifierToBedrockTag; - int commandBlockRuntimeId; Object2IntMap itemFrames; @@ -74,13 +68,4 @@ public class BlockMappings { public boolean isItemFrame(int bedrockBlockRuntimeId) { return this.itemFrames.values().contains(bedrockBlockRuntimeId); } - - /** - * @param cleanJavaIdentifier the clean Java identifier of the block to look up - * - * @return the block tag of the block name mapped from Java to Bedrock. - */ - public NbtMap getBedrockBlockNbt(String cleanJavaIdentifier) { - return this.javaIdentifierToBedrockTag.get(cleanJavaIdentifier); - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java index a202c3f81..ac9a0517a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java @@ -126,11 +126,10 @@ public class BiomeTranslator { storage = new BlockStorage(bitArray, bedrockPalette); } else { storage = new BlockStorage(0); - BitArray bitArray = storage.getBitArray(); // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries for (int i = 0; i < 64; i++) { - int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i)); + int javaId = palette.idToState(biomeData.getStorage().get(i)); int x = i & 3; int y = (i >> 4) & 3; int z = (i >> 2) & 3; @@ -139,7 +138,9 @@ public class BiomeTranslator { int idx = storage.idFor(biomeId); // Convert biome coordinates into block coordinates // Bedrock expects a full 4096 blocks - multiplyIdToStorage(bitArray, idx, x, y, z); + // Implementation note: storage.getBitArray() must be called and not stored - if the palette + // grows, then the instance can change + multiplyIdToStorage(storage.getBitArray(), idx, x, y, z); } } return storage; diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java index 0ec7219c3..94760b66c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java @@ -26,7 +26,10 @@ package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; +import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; /** @@ -59,6 +62,18 @@ public interface BedrockOnlyBlockEntity extends RequiresBlockState { return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); + } else if (BlockStateValues.isCauldron(blockState)) { + // As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim) + return NbtMap.builder() + .putString("id", "Cauldron") + .putByte("isMovable", (byte) 0) + .putShort("PotionId", (short) -1) + .putShort("PotionType", (short) -1) + .putList("Items", NbtType.END, NbtList.EMPTY) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .build(); } return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index 8af5c8af1..1c9ded0c1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java @@ -178,12 +178,8 @@ public class JavaMerchantOffersTranslator extends PacketTranslator bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length); BitSet waterloggedPaletteIds = new BitSet(); - BitSet pistonOrFlowerPaletteIds = new BitSet(); + BitSet bedrockOnlyBlockEntityIds = new BitSet(); BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); int maxBedrockSectionY = (bedrockDimension.height() >> 4) - 1; @@ -144,7 +144,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaId @@ -173,7 +173,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)), javaPalette.idToState(paletteId) @@ -233,9 +233,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>(); + public static final List BEDROCK_ONLY_BLOCK_ENTITIES = List.of( + (BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST), + new FlowerPotBlockEntityTranslator() + ); /** * Contains a list of irregular block entity name translations that can't be fit into the regex */ - public static final Map BLOCK_ENTITY_TRANSLATIONS = new HashMap<>() { - { + public static final Map BLOCK_ENTITY_TRANSLATIONS = Map.of( // Bedrock/Java differences - put(BlockEntityType.ENCHANTING_TABLE, "EnchantTable"); - put(BlockEntityType.JIGSAW, "JigsawBlock"); - put(BlockEntityType.PISTON, "PistonArm"); - put(BlockEntityType.TRAPPED_CHEST, "Chest"); + BlockEntityType.ENCHANTING_TABLE, "EnchantTable", + BlockEntityType.JIGSAW, "JigsawBlock", + BlockEntityType.PISTON, "PistonArm", + BlockEntityType.TRAPPED_CHEST, "Chest" // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly - } - }; - - static { - // Seeing as there are only two - and, hopefully, will only ever be two - we can hardcode this - BEDROCK_ONLY_BLOCK_ENTITIES.add((BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST)); - BEDROCK_ONLY_BLOCK_ENTITIES.add(new FlowerPotBlockEntityTranslator()); - } + ); public static String getBedrockBlockEntityId(BlockEntityType type) { // These are the only exceptions when it comes to block entity ids @@ -77,9 +72,9 @@ public class BlockEntityUtils { String id = type.name(); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already - String[] words = id.split("_");; + String[] words = id.split("_"); for (int i = 0; i < words.length; i++) { - words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + words[i] = words[i].substring(0, 1).toUpperCase(Locale.ROOT) + words[i].substring(1).toLowerCase(Locale.ROOT); } return String.join("", words);