From 94851ef4b80d1ad39de59cd30e78a77b9bb82f82 Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Tue, 31 Dec 2019 00:14:38 +0000 Subject: [PATCH] Move all block related code into BlockTranslator It makes more sense. --- .../network/session/GeyserSession.java | 3 +- .../network/session/cache/ChunkCache.java | 11 +- .../network/translators/TranslatorsInit.java | 10 +- .../network/translators/block/BlockEntry.java | 54 ------- .../translators/block/BlockTranslator.java | 137 +++++++++++++++++- .../geysermc/connector/utils/ChunkUtils.java | 23 ++- .../org/geysermc/connector/utils/Toolbox.java | 103 +------------ 7 files changed, 154 insertions(+), 187 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/block/BlockEntry.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index db13eff95..3de469053 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -57,6 +57,7 @@ import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.Registry; +import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.Toolbox; @@ -319,7 +320,7 @@ public class GeyserSession implements Player { // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setBlockPalette(Toolbox.BLOCKS); + startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(Toolbox.ITEMS); startGamePacket.setVanillaVersion("*"); // startGamePacket.setMovementServerAuthoritative(true); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 3ff1b7f98..7180ede65 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.TranslatorsInit; -import org.geysermc.connector.network.translators.block.BlockEntry; +import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.world.chunk.ChunkPosition; import java.util.HashMap; @@ -69,20 +69,19 @@ public class ChunkCache { } } - public BlockEntry getBlockAt(Position position) { + public BlockState getBlockAt(Position position) { ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); if (!chunks.containsKey(chunkPosition)) - return BlockEntry.AIR; + return BlockTranslator.AIR; Column column = chunks.get(chunkPosition); Chunk chunk = column.getChunks()[position.getY() >> 4]; Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ()); if (chunk != null) { - BlockState blockState = chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); - return TranslatorsInit.getBlockTranslator().getBlockEntry(blockState); + return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); } - return BlockEntry.AIR; + return BlockTranslator.AIR; } public void removeChunk(ChunkPosition position) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java index aa7692830..32900db50 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java @@ -27,7 +27,10 @@ package org.geysermc.connector.network.translators; import com.github.steveice10.mc.protocol.packet.ingame.server.*; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.*; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerHealthPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.*; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; @@ -70,9 +73,6 @@ public class TranslatorsInit { @Getter private static ItemTranslator itemTranslator; - @Getter - private static BlockTranslator blockTranslator; - @Getter private static InventoryTranslator inventoryTranslator = new GenericInventoryTranslator(); @@ -159,7 +159,7 @@ public class TranslatorsInit { Registry.registerBedrock(RespawnPacket.class, new BedrockRespawnTranslator()); itemTranslator = new ItemTranslator(); - blockTranslator = new BlockTranslator(); + BlockTranslator.init(); registerInventoryTranslators(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockEntry.java deleted file mode 100644 index ab126eeb2..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockEntry.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2019 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 lombok.Getter; - -@Getter -public class BlockEntry { - - public static BlockEntry AIR = new BlockEntry("minecraft:air", 0, 0); - - private final String javaIdentifier; - private final int javaId; - private final int bedrockRuntimeId; - private final boolean waterlogged; - - public BlockEntry(String javaIdentifier, int javaId, int bedrockRuntimeId) { - this.javaIdentifier = javaIdentifier; - this.javaId = javaId; - this.bedrockRuntimeId = bedrockRuntimeId; - this.waterlogged = (javaIdentifier.contains("waterlogged=true") - || javaIdentifier.startsWith("minecraft:kelp") - || javaIdentifier.contains("seagrass") - || javaIdentifier.startsWith("minecraft:bubble_column")); - } - - @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof BlockEntry && ((BlockEntry) obj).getBedrockRuntimeId() == this.getBedrockRuntimeId() && ((BlockEntry) obj).getJavaIdentifier().equals(this.getJavaIdentifier())); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index 7ff630c30..c4f31abe9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -1,20 +1,141 @@ 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.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.console.GeyserLogger; import org.geysermc.connector.utils.Toolbox; -import java.util.HashMap; -import java.util.Map; +import java.io.InputStream; +import java.util.*; public class BlockTranslator { - private final Map javaIdentifierMap = new HashMap<>(); + public static final ListTag BLOCKS; + public static final BlockState AIR = new BlockState(0); - public BlockEntry getBlockEntry(BlockState state) { - return Toolbox.BLOCK_ENTRIES.get(state.getId()); + 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 Int2IntMap JAVA_TO_BEDROCK_LIQUID_MAP = new Int2IntOpenHashMap(); + private static final Int2ObjectMap BEDROCK_TO_JAVA_LIQUID_MAP = new Int2ObjectOpenHashMap<>(); + + 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.getAsCompound("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 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) { + addedStatesMap.put(blockTag, bedrockRuntimeId); + paletteList.add(runtimeTag); + } else { + int duplicateRuntimeId = addedStatesMap.get(blockTag); + if (duplicateRuntimeId == -1) { + GeyserLogger.DEFAULT.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); + BEDROCK_TO_JAVA_BLOCK_MAP.put(bedrockRuntimeId, new BlockState(javaRuntimeId)); + + bedrockRuntimeId++; + } + + paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client + + BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); } - public BlockEntry getBlockEntry(String javaIdentifier) { - return javaIdentifierMap.computeIfAbsent(javaIdentifier, key -> Toolbox.BLOCK_ENTRIES.values() - .stream().filter(blockEntry -> blockEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + 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()); + + // 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: + tagBuilder.booleanTag(stateEntry.getKey(), stateValue.booleanValue()); + continue; + case STRING: + tagBuilder.stringTag(stateEntry.getKey(), stateValue.textValue()); + continue; + case NUMBER: + tagBuilder.intTag(stateEntry.getKey(), stateValue.intValue()); + } + } + } + return tagBuilder.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 int getBedrockWaterLoggedId(BlockState state) { + return JAVA_TO_BEDROCK_LIQUID_MAP.getOrDefault(state.getId(), -1); + } + + public static BlockState getJavaLiquidState(int bedrockId) { + return BEDROCK_TO_JAVA_LIQUID_MAP.get(bedrockId); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 93681a7d2..3a94a102d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -10,7 +10,7 @@ import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.TranslatorsInit; -import org.geysermc.connector.network.translators.block.BlockEntry; +import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.world.chunk.ChunkSection; public class ChunkUtils { @@ -34,14 +34,13 @@ public class ChunkUtils { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { BlockState blockState = chunk.get(x, y, z); - BlockEntry block = TranslatorsInit.getBlockTranslator().getBlockEntry(blockState); + int id = BlockTranslator.getBedrockBlockId(blockState); - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), - block.getBedrockRuntimeId()); + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); - if (block.isWaterlogged()) { - BlockEntry water = TranslatorsInit.getBlockTranslator().getBlockEntry("minecraft:water[level=0]"); - section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), water.getBedrockRuntimeId()); + int waterloggedId = BlockTranslator.getBedrockWaterLoggedId(blockState); + if (waterloggedId != -1) { + section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), waterloggedId); } } } @@ -51,22 +50,22 @@ public class ChunkUtils { } public static void updateBlock(GeyserSession session, BlockState blockState, Position position) { - BlockEntry blockEntry = TranslatorsInit.getBlockTranslator().getBlockEntry(blockState); + int blockId = BlockTranslator.getBedrockBlockId(blockState); Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(pos); - updateBlockPacket.setRuntimeId(blockEntry.getBedrockRuntimeId()); + updateBlockPacket.setRuntimeId(blockId); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.getUpstream().sendPacket(updateBlockPacket); UpdateBlockPacket waterPacket = new UpdateBlockPacket(); waterPacket.setDataLayer(1); waterPacket.setBlockPosition(pos); - if (blockEntry.isWaterlogged()) { - BlockEntry water = TranslatorsInit.getBlockTranslator().getBlockEntry("minecraft:water[level=0]"); - waterPacket.setRuntimeId(water.getBedrockRuntimeId()); + int waterloggedId = BlockTranslator.getBedrockWaterLoggedId(blockState); + if (waterloggedId != -1) { + waterPacket.setRuntimeId(waterloggedId); } else { waterPacket.setRuntimeId(0); } 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 6c5775d32..58f6a6556 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -4,20 +4,9 @@ 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.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.console.GeyserLogger; -import org.geysermc.connector.network.translators.block.BlockEntry; import org.geysermc.connector.network.translators.item.ItemEntry; import java.io.InputStream; @@ -28,74 +17,12 @@ 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 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(); static { - - /* Load block palette */ - InputStream stream = 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.getAsCompound("block"), tag) != null) { - throw new AssertionError("Duplicate block states in Bedrock palette"); - } - } - - stream = getResource("mappings/blocks.json"); - JsonNode blocks; - try { - blocks = 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 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) { - addedStatesMap.put(blockTag, bedrockRuntimeId); - paletteList.add(runtimeTag); - } else { - int duplicateRuntimeId = addedStatesMap.get(blockTag); - if (duplicateRuntimeId == -1) { - GeyserLogger.DEFAULT.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++; - } - - BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); - /* Load item palette */ - stream = getResource("bedrock/items.json"); + InputStream stream = getResource("bedrock/items.json"); TypeReference> itemEntriesType = new TypeReference>() { }; @@ -130,33 +57,7 @@ public class Toolbox { } } - private static CompoundTag buildBedrockState(JsonNode node) { - CompoundTagBuilder tagBuilder = CompoundTag.builder(); - tagBuilder.stringTag("name", node.get("bedrock_identifier").textValue()); - - // 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: - tagBuilder.booleanTag(stateEntry.getKey(), stateValue.booleanValue()); - continue; - case STRING: - tagBuilder.stringTag(stateEntry.getKey(), stateValue.textValue()); - continue; - case NUMBER: - tagBuilder.intTag(stateEntry.getKey(), stateValue.intValue()); - } - } - } - return tagBuilder.build("block"); - } - - private static InputStream getResource(String resource) { + public static InputStream getResource(String resource) { InputStream stream = Toolbox.class.getClassLoader().getResourceAsStream(resource); if (stream == null) { throw new AssertionError("Unable to find resource: " + resource);