/* * Copyright (c) 2019-2020 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.connector.network.translators.block; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; 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 gnu.trove.map.TObjectIntMap; import gnu.trove.map.hash.TObjectIntHashMap; import it.unimi.dsi.fastutil.ints.*; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.Toolbox; import java.io.InputStream; import java.util.*; public class BlockTranslator { public static final ListTag BLOCKS; public static final BlockState AIR = new BlockState(0); public static final int BEDROCK_WATER_ID; private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); private static final int BLOCK_STATE_VERSION = 17760256; static { /* Load block palette */ InputStream stream = Toolbox.getResource("bedrock/runtime_block_states.dat"); ListTag blocksTag; try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { blocksTag = (ListTag) nbtInputStream.readTag(); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } Map blockStateMap = new HashMap<>(); for (CompoundTag tag : blocksTag.getValue()) { if (blockStateMap.putIfAbsent(tag.getCompound("block"), tag) != null) { throw new AssertionError("Duplicate block states in Bedrock palette"); } } stream = Toolbox.getResource("mappings/blocks.json"); JsonNode blocks; try { blocks = Toolbox.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } TObjectIntMap addedStatesMap = new TObjectIntHashMap<>(512, 0.5f, -1); List paletteList = new ArrayList<>(); int waterRuntimeId = -1; 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()); if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } boolean waterlogged = entry.getValue().has("waterlogged") && entry.getValue().get("waterlogged").booleanValue(); if (waterlogged) { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId)); WATERLOGGED.add(javaRuntimeId); } else { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, new BlockState(javaRuntimeId)); } CompoundTag runtimeTag = blockStateMap.remove(blockTag); if (runtimeTag != null) { addedStatesMap.put(blockTag, bedrockRuntimeId); paletteList.add(runtimeTag); } else { int duplicateRuntimeId = addedStatesMap.get(blockTag); if (duplicateRuntimeId == -1) { GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); } else { JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, duplicateRuntimeId); } continue; } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); bedrockRuntimeId++; } if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } BEDROCK_WATER_ID = waterRuntimeId; paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); } private BlockTranslator() { } public static void init() { // no-op } private static CompoundTag buildBedrockState(JsonNode node) { CompoundTagBuilder tagBuilder = CompoundTag.builder(); tagBuilder.stringTag("name", node.get("bedrock_identifier").textValue()) .intTag("version", BlockTranslator.BLOCK_STATE_VERSION); CompoundTagBuilder statesBuilder = CompoundTag.builder(); // check for states if (node.has("bedrock_states")) { Iterator> statesIterator = node.get("bedrock_states").fields(); while (statesIterator.hasNext()) { Map.Entry stateEntry = statesIterator.next(); JsonNode stateValue = stateEntry.getValue(); switch (stateValue.getNodeType()) { case BOOLEAN: statesBuilder.booleanTag(stateEntry.getKey(), stateValue.booleanValue()); continue; case STRING: statesBuilder.stringTag(stateEntry.getKey(), stateValue.textValue()); continue; case NUMBER: statesBuilder.intTag(stateEntry.getKey(), stateValue.intValue()); } } } return tagBuilder.tag(statesBuilder.build("states")).build("block"); } public static int getBedrockBlockId(BlockState state) { return JAVA_TO_BEDROCK_BLOCK_MAP.get(state.getId()); } public static BlockState getJavaBlockState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } public static boolean isWaterlogged(BlockState state) { return WATERLOGGED.contains(state.getId()); } public static BlockState getJavaWaterloggedState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); } }