diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 00d7171cf..408ac12c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -27,11 +27,17 @@ package org.geysermc.connector.network.translators.world.block; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.ints.Int2BooleanMap; +import it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ByteMap; import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.HashMap; import java.util.Map; /** @@ -41,7 +47,11 @@ public class BlockStateValues { private static final Object2IntMap BANNER_COLORS = new Object2IntOpenHashMap<>(); private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); + private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); + private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); private static final Object2IntMap NOTEBLOCK_PITCHES = new Object2IntOpenHashMap<>(); + private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); + private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); private static final Object2ByteMap SHULKERBOX_DIRECTIONS = new Object2ByteOpenHashMap<>(); @@ -64,12 +74,25 @@ public class BlockStateValues { return; } + if (entry.getKey().contains("potted_")) { + System.out.println(entry.getKey().replace("potted_", "")); + FLOWER_POT_VALUES.put(javaBlockState.getId(), entry.getKey().replace("potted_", "")); + return; + } + JsonNode notePitch = entry.getValue().get("note_pitch"); if (notePitch != null) { NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); return; } + if (entry.getKey().contains("piston")) { + // True if extended, false if not + PISTON_VALUES.put(javaBlockState.getId(), entry.getKey().contains("extended=true")); + IS_STICKY_PISTON.put(javaBlockState.getId(), entry.getKey().contains("sticky")); + return; + } + JsonNode skullVariation = entry.getValue().get("variation"); if(skullVariation != null) { SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); @@ -114,6 +137,22 @@ public class BlockStateValues { return -1; } + /** + * Get the Int2ObjectMap of flower pot block states to containing plant + * @return Int2ObjectMap of flower pot values + */ + public static Int2ObjectMap getFlowerPotValues() { + return FLOWER_POT_VALUES; + } + + /** + * Get the map of contained flower pot plants to Bedrock CompoundTag + * @return Map of flower pot blocks. + */ + public static Map getFlowerPotBlocks() { + return FLOWER_POT_BLOCKS; + } + /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. @@ -127,6 +166,18 @@ public class BlockStateValues { return -1; } + /** + * Get the Int2BooleanMap showing if a piston block state is extended or not. + * @return the Int2BooleanMap of piston extensions. + */ + public static Int2BooleanMap getPistonValues() { + return PISTON_VALUES; + } + + public static boolean isStickyPiston(BlockState blockState) { + return IS_STICKY_PISTON.get(blockState.getId()); + } + /** * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. * This gives a byte variant ID that Bedrock can use. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 2bb918cc8..e614900cb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -153,6 +153,11 @@ public class BlockTranslator { BlockStateValues.storeBlockStateValues(entry, javaBlockState); + // Get the tag needed for non-empty flower pots + if (entry.getValue().get("pottable") != null) { + BlockStateValues.getFlowerPotBlocks().put(entry.getKey().split("\\[")[0], buildBedrockState(entry.getValue())); + } + if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java new file mode 100644 index 000000000..5b325ebad --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -0,0 +1,61 @@ +/* + * 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.world.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Implemented only if a block is a block entity in Bedrock and not Java Edition. + */ +public interface BedrockOnlyBlockEntity { + + /** + * Update the block on Bedrock Edition. + * @param session GeyserSession. + * @param blockState The Java block state. + * @param position The Bedrock block position. + */ + void updateBlock(GeyserSession session, BlockState blockState, Vector3i position); + + /** + * Get the tag of the Bedrock-only block entity + * @param position Bedrock position of block. + * @param blockState Java BlockState of block. + * @return Bedrock tag, or null if not a Bedrock-only Block Entity + */ + static CompoundTag getTag(Vector3i position, BlockState blockState) { + if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { + return FlowerPotBlockEntityTranslator.getTag(blockState, position); + } else if (PistonBlockEntityTranslator.isBlock(blockState)) { + return PistonBlockEntityTranslator.getTag(blockState, position); + } + return null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java new file mode 100644 index 000000000..6861a9535 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -0,0 +1,84 @@ +/* + * 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.world.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.BlockEntityUtils; + +public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return (BlockStateValues.getFlowerPotValues().containsKey(blockState.getId())); + } + + @Override + public void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) { + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setBlockPosition(position); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + session.getUpstream().sendPacket(updateBlockPacket); + } + + /** + * Get the Nukkit CompoundTag of the flower pot. + * @param blockState Java BlockState of flower pot. + * @param position Bedrock position of flower pot. + * @return Bedrock tag of flower pot. + */ + public static CompoundTag getTag(BlockState blockState, Vector3i position) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .byteTag("isMovable", (byte) 1) + .stringTag("id", "FlowerPot"); + // Get the Java name of the plant inside. e.g. minecraft:oak_sapling + String name = BlockStateValues.getFlowerPotValues().get(blockState.getId()); + if (name != null) { + // Get the Bedrock CompoundTag of the block. + // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. + CompoundTag plant = BlockStateValues.getFlowerPotBlocks().get(name); + if (plant != null) { + tagBuilder.tag(plant.toBuilder().build("PlantBlock")); + } + } + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java new file mode 100644 index 000000000..2dffce24c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java @@ -0,0 +1,72 @@ +/* + * 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.world.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; + +/** + * Pistons are a special case where they are only a block entity on Bedrock. + */ +public class PistonBlockEntityTranslator { + + /** + * Used in ChunkUtils to determine if the block is a piston. + * @param blockState Java BlockState of block. + * @return if block is a piston or not. + */ + public static boolean isBlock(BlockState blockState) { + return BlockStateValues.getPistonValues().containsKey(blockState.getId()); + } + + /** + * Calculates the Nukkit CompoundTag to send to the client on chunk + * @param blockState Java BlockState of block. + * @param position Bedrock position of piston. + * @return Bedrock tag of piston. + */ + public static CompoundTag getTag(BlockState blockState, Vector3i position) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .byteTag("isMovable", (byte) 1) + .stringTag("id", "PistonArm"); + if (BlockStateValues.getPistonValues().containsKey(blockState.getId())) { + boolean extended = BlockStateValues.getPistonValues().get(blockState.getId()); + // 1f if extended, otherwise 0f + tagBuilder.floatTag("Progress", (extended) ? 1.0f : 0.0f); + // 1 if sticky, 0 if not + tagBuilder.byteTag("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + } + return tagBuilder.buildRootTag(); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index f79ffd5f7..b0794e200 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -42,8 +42,12 @@ public class BlockEntityUtils { } public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Position position) { + updateBlockEntity(session, blockEntity, Vector3i.from(position.getX(), position.getY(), position.getZ())); + } + + public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Vector3i position) { BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); - blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); + blockEntityPacket.setBlockPosition(position); blockEntityPacket.setData(blockEntity); session.getUpstream().sendPacket(blockEntityPacket); } 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 97be97324..fcc596eb1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -35,11 +35,13 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.Getter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.entity.*; import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.world.block.BlockTranslator; @@ -68,6 +70,9 @@ public class ChunkUtils { // Temporarily stores positions of BlockState values per chunk load Map blockEntityPositions = new HashMap<>(); + // Temporarily stores compound tags of Bedrock-only block entities + ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); + for (int chunkY = 0; chunkY < chunks.length; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; @@ -99,6 +104,13 @@ public class ChunkUtils { section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); } + // Check if block is piston or flower - only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(blockState.getId()) || + BlockStateValues.getPistonValues().containsKey(blockState.getId())) { + Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(Vector3i.from(pos.getX(), pos.getY(), pos.getZ()), blockState)); + } + if (BlockTranslator.isWaterlogged(blockState)) { section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID); } @@ -108,8 +120,9 @@ public class ChunkUtils { } - com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length]; - for (int i = 0; i < blockEntities.length; i++) { + com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length + bedrockOnlyBlockEntities.size()]; + int i = 0; + while (i < blockEntities.length) { CompoundTag tag = blockEntities[i]; String tagName; if (!tag.contains("id")) { @@ -121,8 +134,14 @@ public class ChunkUtils { String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); - BlockState blockState = blockEntityPositions.get(new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue())); + Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); + BlockState blockState = blockEntityPositions.get(pos); bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); + i++; + } + for (com.nukkitx.nbt.tag.CompoundTag tag : bedrockOnlyBlockEntities) { + bedrockBlockEntities[i] = tag; + i++; } chunkData.blockEntities = bedrockBlockEntities; @@ -186,6 +205,11 @@ public class ChunkUtils { // Iterates through all block entity translators and determines if the block state needs to be saved for (RequiresBlockState requiresBlockState : Translators.getRequiresBlockStateMap()) { if (requiresBlockState.isBlock(blockState)) { + // Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks + if (requiresBlockState instanceof BedrockOnlyBlockEntity) { + ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); + break; + } CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); break; //No block will be a part of two classes } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index ddb62693f..5b3a9ad1d 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit ddb62693f878a99f106a0d6ea16a92ec7c4c7cd0 +Subproject commit 5b3a9ad1d2ef76105fb318e63126a096844b3195