diff --git a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java index 93ac5e457..2f15094ef 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -66,11 +66,6 @@ public class BlockRegistries { */ public static final ListRegistry BLOCK_STATES = ListRegistry.create(RegistryLoaders.empty(ArrayList::new)); - /** - * A mapped registry which stores Java to Bedrock block identifiers. - */ - public static final SimpleMappedRegistry JAVA_TO_BEDROCK_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); - /** * A mapped registry containing which holds block IDs to its {@link BlockCollision}. */ @@ -89,6 +84,9 @@ public class BlockRegistries { /** * A registry containing all the waterlogged blockstates. + * Properties.WATERLOGGED should not be relied on for two reasons: + * - Custom blocks + * - Seagrass, kelp, and bubble columns are assumed waterlogged and don't have a waterlogged property */ public static final SimpleRegistry WATERLOGGED = SimpleRegistry.create(RegistryLoaders.empty(BitSet::new)); @@ -144,7 +142,6 @@ public class BlockRegistries { CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.CUSTOM_REGISTRATION); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.POST_INIT); - System.out.println("Block registries loaded"); } public static void init() { diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 7f0d9013a..54d013140 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -204,6 +204,5 @@ public final class Registries { biomesNbt.put(key, value.build()); } BIOMES_NBT.set(biomesNbt.build()); - System.out.println("Registries loaded"); } } 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 a0af4cfa0..5f50f4f68 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 @@ -236,6 +236,10 @@ public final class BlockRegistryPopulator { GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE]; GeyserBedrockBlock[] javaToVanillaBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE]; + //List javaToBedrockIdentifiers = new ArrayList<>(BlockRegistries.JAVA_BLOCKS.get().size()); + var javaToBedrockIdentifiers = new Int2ObjectOpenHashMap(); + Block lastBlockSeen = null; + // Stream isn't ideal. List javaPottable = BlockRegistries.JAVA_BLOCKS.get() .parallelStream() @@ -291,27 +295,35 @@ public final class BlockRegistryPopulator { case "minecraft:moving_piston[facing=north,type=normal]" -> movingBlockDefinition = bedrockDefinition; } - if (blockState.block() == Blocks.JIGSAW) { + Block block = blockState.block(); + if (block != lastBlockSeen) { + lastBlockSeen = block; + String bedrockName = bedrockDefinition.getState().getString("name"); + if (!block.javaIdentifier().toString().equals(bedrockName)) { + javaToBedrockIdentifiers.put(block.javaId(), bedrockName.substring("minecraft:".length()).intern()); + } + } + + if (block == Blocks.JIGSAW) { jigsawDefinitions.add(bedrockDefinition); } - if (blockState.block() == Blocks.STRUCTURE_BLOCK) { + if (block == Blocks.STRUCTURE_BLOCK) { String mode = blockState.getValue(Properties.STRUCTUREBLOCK_MODE); structureBlockDefinitions.put(mode.toUpperCase(Locale.ROOT), bedrockDefinition); } boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false) - || blockState.block() == Blocks.BUBBLE_COLUMN || blockState.block() == Blocks.KELP || blockState.block() == Blocks.SEAGRASS; + || block == Blocks.BUBBLE_COLUMN || block == Blocks.KELP || block == Blocks.SEAGRASS; if (waterlogged) { - int finalJavaRuntimeId = javaRuntimeId; - BlockRegistries.WATERLOGGED.register(set -> set.set(finalJavaRuntimeId)); + BlockRegistries.WATERLOGGED.get().set(javaRuntimeId); } // Get the tag needed for non-empty flower pots - if (javaPottable.contains(blockState.block())) { + if (javaPottable.contains(block)) { // Specifically NOT putIfAbsent - mangrove propagule breaks otherwise - flowerPotBlocks.put(blockState.block(), blockStates.get(bedrockDefinition.getRuntimeId())); + flowerPotBlocks.put(block, blockStates.get(bedrockDefinition.getRuntimeId())); } javaToVanillaBedrockBlocks[javaRuntimeId] = vanillaBedrockDefinition; @@ -367,9 +379,12 @@ public final class BlockRegistryPopulator { javaToVanillaBedrockBlocks[stateRuntimeId] = bedrockDefinition; // TODO: Check this? javaToBedrockBlocks[stateRuntimeId] = bedrockDefinition; + javaToBedrockIdentifiers.put(entry.getKey().stateGroupId(), entry.getValue().block().identifier()); } } + javaToBedrockIdentifiers.trim(); + // Loop around again to find all item frame runtime IDs Object2ObjectMaps.fastForEach(blockStateOrderedMap, entry -> { String name = entry.getKey().getString("name"); @@ -381,6 +396,7 @@ public final class BlockRegistryPopulator { BlockRegistries.BLOCKS.register(palette.valueInt(), builder.bedrockRuntimeMap(bedrockRuntimeMap) .javaToBedrockBlocks(javaToBedrockBlocks) .javaToVanillaBedrockBlocks(javaToVanillaBedrockBlocks) + .javaToBedrockIdentifiers(javaToBedrockIdentifiers) .stateDefinitionMap(blockStateOrderedMap) .itemFrames(itemFrames) .flowerPotBlocks(flowerPotBlocks) @@ -484,15 +500,9 @@ public final class BlockRegistryPopulator { }; block.setJavaId(javaBlockState.stateGroupId()); - String bedrockIdentifier = customBlockState.block().identifier(); - BlockRegistries.JAVA_BLOCKS.get().add(javaBlockState.stateGroupId(), block); //TODO don't allow duplicates, allow blanks BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId); BlockRegistries.BLOCK_STATES.register(stateRuntimeId, new BlockState(block, stateRuntimeId)); - - // Keeping this here since this is currently unchanged between versions - // It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions - BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern()); } } 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 e4b7111b3..f618fde46 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 @@ -52,6 +52,12 @@ public class BlockMappings implements DefinitionRegistry { GeyserBedrockBlock[] javaToBedrockBlocks; GeyserBedrockBlock[] javaToVanillaBedrockBlocks; + /** + * Java block ID -> Bedrock block ID (without minecraft:), IF they are different + * While Bedrock is progressing slowly through their flattening, some Bedrock identifiers may differ. + */ + Int2ObjectMap javaToBedrockIdentifiers; + Map stateDefinitionMap; GeyserBedrockBlock[] bedrockRuntimeMap; int[] remappedVanillaIds; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 768c94791..251aacba8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -42,6 +42,7 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.CustomSkull; @@ -52,20 +53,12 @@ import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.InventoryUtils; -import org.geysermc.mcprotocollib.protocol.data.game.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.ModifierOperation; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.*; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public final class ItemTranslator { @@ -185,11 +178,11 @@ public final class ItemTranslator { translateCustomItem(components, builder, bedrockItem); if (components != null) { - // Translate the canDestroy and canPlaceOn Java NBT + // Translate the canDestroy and canPlaceOn Java components AdventureModePredicate canDestroy = components.get(DataComponentType.CAN_BREAK); AdventureModePredicate canPlaceOn = components.get(DataComponentType.CAN_PLACE_ON); - String[] canBreak = getCanModify(canDestroy); - String[] canPlace = getCanModify(canPlaceOn); + String[] canBreak = getCanModify(session, canDestroy); + String[] canPlace = getCanModify(session, canPlaceOn); if (canBreak != null) { builder.canBreak(canBreak); } @@ -325,27 +318,42 @@ public final class ItemTranslator { * @param canModifyJava the list of items in Java * @return the new list of items in Bedrock */ - // TODO this is now more complicated in 1.20.5. Yippee! - private static String @Nullable [] getCanModify(@Nullable AdventureModePredicate canModifyJava) { + // TODO blocks by tag, maybe NBT, maybe properties + // Blocks by tag will be easy enough, most likely, we just need to... save all block tags. + // Probably do that with Guava interning around sessions + private static String @Nullable [] getCanModify(GeyserSession session, @Nullable AdventureModePredicate canModifyJava) { if (canModifyJava == null) { return null; } List predicates = canModifyJava.getPredicates(); - if (predicates.size() > 0) { - String[] canModifyBedrock = new String[predicates.size()]; - for (int i = 0; i < canModifyBedrock.length; i++) { - // Get the Java identifier of the block that can be placed - String location = predicates.get(i).getLocation(); - if (location == null) { - canModifyBedrock[i] = ""; // So it'll serialize - continue; // ??? + if (!predicates.isEmpty()) { + List canModifyBedrock = new ArrayList<>(); // This used to be an array, but we need to be flexible with what blocks can be supported + for (int i = 0; i < predicates.size(); i++) { + HolderSet holderSet = predicates.get(i).getBlocks(); + if (holderSet == null) { + continue; + } + int[] holders = holderSet.getHolders(); + if (holders == null) { + continue; + } + // Holders is an int state of Java block IDs (not block states) + for (int blockId : holders) { + // Get the Bedrock identifier of the item + // This will unfortunately be limited - for example, beds and banners will be translated weirdly + Block block = BlockRegistries.JAVA_BLOCKS.get(blockId); + if (block == null) { + continue; + } + String identifier = session.getBlockMappings().getJavaToBedrockIdentifiers().get(block.javaId()); + if (identifier == null) { + canModifyBedrock.add(block.javaIdentifier().value()); + } else { + canModifyBedrock.add(identifier); + } } - String block = Identifier.formalize(location); - // Get the Bedrock identifier of the item and replace it. - // This will unfortunately be limited - for example, beds and banners will be translated weirdly - canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", ""); } - return canModifyBedrock; + return canModifyBedrock.toArray(new String[0]); } return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index 706997402..d9f190197 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -59,6 +59,7 @@ import java.util.*; @Translator(packet = ClientboundCommandsPacket.class) public class JavaCommandsTranslator extends PacketTranslator { + private static final String[] ALL_BLOCK_NAMES = BlockRegistries.JAVA_BLOCKS.get().stream().map(block -> block.javaIdentifier().toString()).toArray(String[]::new); private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers(); private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.values().stream().map(AttributeType::getIdentifier).toList().toArray(new String[0]); private static final String[] ENUM_BOOLEAN = {"true", "false"}; @@ -246,7 +247,7 @@ public class JavaCommandsTranslator extends PacketTranslator CommandParam.FILE_PATH; case BOOL -> ENUM_BOOLEAN; case OPERATION -> CommandParam.OPERATOR; // ">=", "==", etc - case BLOCK_STATE -> context.getBlockStates(); + case BLOCK_STATE -> ALL_BLOCK_NAMES; case ITEM_STACK -> context.getItemNames(); case COLOR -> VALID_COLORS; case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS; @@ -286,7 +287,6 @@ public class JavaCommandsTranslator extends PacketTranslator