diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 9522e391a..6eeaf0430 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -1,16 +1,21 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.ListTag; -import com.nukkitx.nbt.tag.Tag; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; - +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.console.GeyserLogger; import org.geysermc.connector.network.translators.block.BlockEntry; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -20,105 +25,146 @@ import java.util.*; public class Toolbox { + public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + public static final Collection ITEMS = new ArrayList<>(); - public static ListTag BLOCKS; + public static final ListTag BLOCKS; public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); public static final Int2ObjectMap BLOCK_ENTRIES = new Int2ObjectOpenHashMap<>(); + public static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); + public static final Int2IntMap JAVA_TO_BEDROCK_LIQUID_MAP = new Int2IntOpenHashMap(); - public static void init() { - InputStream stream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/runtime_block_states.dat"); - if (stream == null) { - throw new AssertionError("Unable to find bedrock/runtime_block_states.dat"); - } + static { + + /* Load block palette */ + InputStream stream = getResource("bedrock/runtime_block_states.dat"); ListTag blocksTag; - - NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream); - try { + try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { blocksTag = (ListTag) nbtInputStream.readTag(); - nbtInputStream.close(); - } catch (Exception ex) { - GeyserLogger.DEFAULT.warning("Failed to get blocks from runtime block states, please report this error!"); - throw new AssertionError(ex); - } - - BLOCKS = blocksTag; - InputStream stream2 = Toolbox.class.getClassLoader().getResourceAsStream("bedrock/items.json"); - if (stream2 == null) { - throw new AssertionError("Items Table not found"); - } - - ObjectMapper startGameItemMapper = new ObjectMapper(); - List startGameItems = new ArrayList<>(); - try { - startGameItems = startGameItemMapper.readValue(stream2, ArrayList.class); } catch (Exception e) { - e.printStackTrace(); + throw new AssertionError("Unable to get blocks from runtime block states", e); } - for (Map entry : startGameItems) { - ITEMS.add(new StartGamePacket.ItemEntry((String) entry.get("name"), (short) ((int) entry.get("id")))); + Map blockStateMap = new HashMap<>(); + + for (CompoundTag tag : blocksTag.getValue()) { + if (blockStateMap.putIfAbsent(tag.getAsCompound("block"), tag) != null) { + throw new AssertionError("Duplicate block states in Bedrock palette"); + } } - InputStream itemStream = Toolbox.class.getClassLoader().getResourceAsStream("mappings/items.json"); - ObjectMapper itemMapper = new ObjectMapper(); - Map> items = new HashMap<>(); - + stream = getResource("mappings/blocks.json"); + JsonNode blocks; try { - items = itemMapper.readValue(itemStream, LinkedHashMap.class); - } catch (Exception ex) { - ex.printStackTrace(); + blocks = JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java block mappings", e); + } + TObjectIntMap stateRuntimeMap = new TObjectIntHashMap<>(512, 0.5f, -1); + List paletteList = new ArrayList<>(); + + int javaRuntimeId = -1; + int bedrockRuntimeId = 0; + Iterator> blocksIterator = blocks.fields(); + while (blocksIterator.hasNext()) { + javaRuntimeId++; + Map.Entry entry = blocksIterator.next(); + String javaId = entry.getKey(); + CompoundTag blockTag = buildBedrockState(entry.getValue()); + + CompoundTag runtimeTag = blockStateMap.remove(blockTag); + if (runtimeTag != null) { + stateRuntimeMap.put(blockTag, javaRuntimeId); + paletteList.add(runtimeTag); + } else { + int duplicateRuntimeId = stateRuntimeMap.get(blockTag); + if (duplicateRuntimeId == -1) { + GeyserLogger.DEFAULT.debug("Mapping " + javaId + " was not found for bedrock edition!"); + } else { + JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); + } + continue; + } + JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); + + bedrockRuntimeId++; + } + + BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); + + /* Load item palette */ + stream = getResource("bedrock/items.json"); + + TypeReference> itemEntriesType = new TypeReference>() { + }; + + List itemEntries; + try { + itemEntries = JSON_MAPPER.readValue(stream, itemEntriesType); + } catch (Exception e) { + throw new AssertionError("Unable to load Bedrock runtime item IDs", e); + } + + for (JsonNode entry : itemEntries) { + ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + } + + stream = getResource("mappings/items.json"); + + JsonNode items; + try { + items = JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java runtime item IDs", e); } int itemIndex = 0; - for (Map.Entry> itemEntry : items.entrySet()) { - ITEM_ENTRIES.put(itemIndex, new ItemEntry(itemEntry.getKey(), itemIndex, (int) itemEntry.getValue().get("bedrock_id"), (int) itemEntry.getValue().get("bedrock_data"))); + Iterator> iterator = items.fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + ITEM_ENTRIES.put(itemIndex, new ItemEntry(entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); itemIndex++; } + } - InputStream blockStream = Toolbox.class.getClassLoader().getResourceAsStream("mappings/blocks.json"); - ObjectMapper blockMapper = new ObjectMapper(); - Map> blocks = new HashMap<>(); + private static CompoundTag buildBedrockState(JsonNode node) { + CompoundTagBuilder tagBuilder = CompoundTag.builder(); + tagBuilder.stringTag("name", node.get("bedrock_identifier").textValue()); - try { - blocks = blockMapper.readValue(blockStream, LinkedHashMap.class); - } catch (Exception ex) { - ex.printStackTrace(); - } + // check for states + if (node.has("bedrock_states")) { + Iterator> statesIterator = node.get("bedrock_states").fields(); - int javaIndex = -1; - javaLoop: - for (Map.Entry> javaEntry : blocks.entrySet()) { - javaIndex++; - String wantedIdentifier = (String) javaEntry.getValue().get("bedrock_identifier"); - Map wantedStates = (Map) javaEntry.getValue().get("bedrock_states"); - - int bedrockIndex = -1; - bedrockLoop: - for (CompoundTag bedrockEntry : BLOCKS.getValue()) { - bedrockIndex++; - CompoundTag blockTag = bedrockEntry.getAsCompound("block"); - if (blockTag.getAsString("name").equals(wantedIdentifier)) { - if (wantedStates != null) { - Map> bedrockStates = blockTag.getAsCompound("states").getValue(); - for (Map.Entry stateEntry : wantedStates.entrySet()) { - Tag bedrockStateTag = bedrockStates.get(stateEntry.getKey()); - if (bedrockStateTag == null) - continue bedrockLoop; - Object bedrockStateValue = bedrockStateTag.getValue(); - if (bedrockStateValue instanceof Byte) - bedrockStateValue = ((Byte) bedrockStateValue).intValue(); - if (!stateEntry.getValue().equals(bedrockStateValue)) - continue bedrockLoop; - } - } - BlockEntry blockEntry = new BlockEntry(javaEntry.getKey(), javaIndex, bedrockIndex); - BLOCK_ENTRIES.put(javaIndex, blockEntry); - continue javaLoop; + while (statesIterator.hasNext()) { + Map.Entry stateEntry = statesIterator.next(); + JsonNode stateValue = stateEntry.getValue(); + switch (stateValue.getNodeType()) { + case BOOLEAN: + tagBuilder.booleanTag(stateEntry.getKey(), stateValue.booleanValue()); + continue; + case STRING: + tagBuilder.stringTag(stateEntry.getKey(), stateValue.textValue()); + continue; + case NUMBER: + tagBuilder.intTag(stateEntry.getKey(), stateValue.intValue()); } } - GeyserLogger.DEFAULT.debug("Mapping " + javaEntry.getKey() + " was not found for bedrock edition!"); } + return tagBuilder.build("block"); + } + + private static InputStream getResource(String resource) { + InputStream stream = Toolbox.class.getClassLoader().getResourceAsStream(resource); + if (stream == null) { + throw new AssertionError("Unable to find resource: " + resource); + } + return stream; + } + + public static void init() { + // no-op } } \ No newline at end of file