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 6539fb05..8e3728e6 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 @@ -48,6 +48,9 @@ import com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; import org.geysermc.connector.network.translators.bedrock.*; import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; +import org.geysermc.connector.network.translators.block.entity.EmptyBlockEntityTranslator; +import org.geysermc.connector.network.translators.block.entity.SignBlockEntityTranslator; import org.geysermc.connector.network.translators.inventory.GenericInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; @@ -67,6 +70,8 @@ import org.geysermc.connector.network.translators.java.world.*; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; public class TranslatorsInit { @@ -79,6 +84,9 @@ public class TranslatorsInit { @Getter private static InventoryTranslator inventoryTranslator = new GenericInventoryTranslator(); + @Getter + private static Map blockEntityTranslators = new HashMap<>(); + private static final CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; @@ -143,6 +151,7 @@ public class TranslatorsInit { Registry.registerJava(ServerBlockChangePacket.class, new JavaBlockChangeTranslator()); Registry.registerJava(ServerMultiBlockChangePacket.class, new JavaMultiBlockChangeTranslator()); Registry.registerJava(ServerUnloadChunkPacket.class, new JavaUnloadChunkTranslator()); + Registry.registerJava(ServerUpdateTileEntityPacket.class, new JavaUpdateTileEntityTranslator()); Registry.registerJava(ServerOpenWindowPacket.class, new OpenWindowPacketTranslator()); @@ -159,9 +168,15 @@ public class TranslatorsInit { itemTranslator = new ItemTranslator(); blockTranslator = new BlockTranslator(); + registerBlockEntityTranslators(); registerInventoryTranslators(); } + private static void registerBlockEntityTranslators() { + blockEntityTranslators.put("Empty", new EmptyBlockEntityTranslator()); + blockEntityTranslators.put("Sign", new SignBlockEntityTranslator()); + } + private static void registerInventoryTranslators() { /*inventoryTranslators.put(WindowType.GENERIC_9X1, new GenericInventoryTranslator()); inventoryTranslators.put(WindowType.GENERIC_9X2, new GenericInventoryTranslator()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java new file mode 100644 index 00000000..4913a300 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java @@ -0,0 +1,73 @@ +/* + * 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.entity; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.utils.BlockEntityUtils; + +import java.util.List; + +public abstract class BlockEntityTranslator { + + public abstract List> translateTag(CompoundTag tag); + + public abstract CompoundTag getDefaultJavaTag(int x, int y, int z); + + public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(int x, int y, int z); + + public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(CompoundTag tag) { + String id = BlockEntityUtils.getBedrockBlockEntityId((String) tag.get("id").getValue()); + int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); + int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); + int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + + CompoundTagBuilder tagBuilder = getConstantBedrockTag(id, x, y, z).toBuilder(); + translateTag(tag).forEach(tagBuilder::tag); + return tagBuilder.buildRootTag(); + } + + protected CompoundTag getConstantJavaTag(String id, int x, int y, int z) { + CompoundTag tag = new CompoundTag(""); + tag.put(new IntTag("x", x)); + tag.put(new IntTag("y", y)); + tag.put(new IntTag("z", z)); + tag.put(new StringTag("id", id)); + return tag; + } + + protected com.nukkitx.nbt.tag.CompoundTag getConstantBedrockTag(String id, int x, int y, int z) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", x) + .intTag("y", y) + .intTag("z", z) + .stringTag("id", id); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java new file mode 100644 index 00000000..927337bc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java @@ -0,0 +1,50 @@ +/* + * 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.entity; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.tag.Tag; + +import java.util.ArrayList; +import java.util.List; + +public class EmptyBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag) { + return new ArrayList<>(); + } + + @Override + public CompoundTag getDefaultJavaTag(int x, int y, int z) { + return getConstantJavaTag("empty", x, y, z); + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(int x, int y, int z) { + return getConstantBedrockTag("Empty", x, y, z); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java new file mode 100644 index 00000000..414455c7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java @@ -0,0 +1,77 @@ +/* + * 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.entity; + +import com.github.steveice10.mc.protocol.data.message.Message; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SignBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag) { + List> tags = new ArrayList<>(); + + String line1 = (String) tag.getValue().get("Text1").getValue(); + String line2 = (String) tag.getValue().get("Text2").getValue(); + String line3 = (String) tag.getValue().get("Text3").getValue(); + String line4 = (String) tag.getValue().get("Text4").getValue(); + + tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(Message.fromString(line1)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line2)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line3)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line4)) + )); + + tags.add(new ByteTag("isMovable", (byte) 0)); + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(int x, int y, int z) { + CompoundTag tag = getConstantJavaTag("minecraft:sign", x, y, z); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text1", "\"text\":\"\"")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text2", "\"text\":\"\"")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text3", "\"text\":\"\"")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text4", "\"text\":\"\"")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag("Sign", x, y, z).toBuilder(); + tagBuilder.stringTag("Text", ""); + tagBuilder.byteTag("isMovable", (byte) 0); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index a620197e..1c83579a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -3,10 +3,14 @@ package org.geysermc.connector.network.translators.java.world; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTOutputStream; +import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import org.geysermc.api.Geyser; import org.geysermc.connector.network.session.GeyserSession; @@ -53,6 +57,14 @@ public class JavaChunkDataTranslator extends PacketTranslator { + + @Override + public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { + BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); + blockEntityPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ()) + ); + + String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); + BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id); + blockEntityPacket.setData(translator.getBlockEntityTag(packet.getNbt())); + session.getUpstream().sendPacket(blockEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java new file mode 100644 index 00000000..6fb62b55 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -0,0 +1,32 @@ +package org.geysermc.connector.utils; + +import org.geysermc.connector.network.translators.TranslatorsInit; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; + +public class BlockEntityUtils { + + public static String getBedrockBlockEntityId(String id) { + // This is the only exception when it comes to block entity ids + if (id.contains("piston_head")) + return "PistonArm"; + + id = id.replace("minecraft:", ""); + id = id.replace("_", " "); + String[] words = id.split(" "); + for (int i = 0; i < words.length; i++) { + words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + } + + id = String.join(" ", words); + return id.replace(" ", ""); + } + + public static BlockEntityTranslator getBlockEntityTranslator(String name) { + BlockEntityTranslator blockEntityTranslator = TranslatorsInit.getBlockEntityTranslators().get(name); + if (blockEntityTranslator == null) { + return TranslatorsInit.getBlockEntityTranslators().get("Empty"); + } + + return blockEntityTranslator; + } +} 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 6f2b6d3c..59a5fe84 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -3,10 +3,19 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.console.GeyserLogger; import org.geysermc.connector.network.translators.TranslatorsInit; import org.geysermc.connector.network.translators.block.BlockEntry; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; +import org.geysermc.connector.network.translators.block.entity.SignBlockEntityTranslator; import org.geysermc.connector.world.chunk.ChunkSection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class ChunkUtils { public static ChunkData translateToBedrock(Column column) { @@ -16,6 +25,7 @@ public class ChunkUtils { int chunkSectionCount = chunks.length; chunkData.sections = new ChunkSection[chunkSectionCount]; + List blockEntities = new ArrayList<>(Arrays.asList(column.getTileEntities())); for (int chunkY = 0; chunkY < chunkSectionCount; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; @@ -38,10 +48,32 @@ public class ChunkUtils { section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), 9 << 4); // water id } + + // Block entity data for signs is not sent in this packet, which is needed + // for bedrock, so we need to check the block itself + if (block.getJavaIdentifier().contains("sign")) { + SignBlockEntityTranslator sign = (SignBlockEntityTranslator) BlockEntityUtils.getBlockEntityTranslator("Sign"); + blockEntities.add(sign.getDefaultJavaTag(x, y, z)); + } } } } } + + List bedrockBlockEntities = new ArrayList<>(); + for (CompoundTag tag : blockEntities) { + Tag idTag = tag.get("id"); + if (idTag == null) { + GeyserLogger.DEFAULT.debug("Got tag with no id: " + tag.getValue()); + continue; + } + + String id = BlockEntityUtils.getBedrockBlockEntityId((String) tag.get("id").getValue()); + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); + bedrockBlockEntities.add(blockEntityTranslator.getBlockEntityTag(tag)); + } + + chunkData.blockEntities = bedrockBlockEntities; return chunkData; } @@ -49,6 +81,6 @@ public class ChunkUtils { public ChunkSection[] sections; public byte[] biomes = new byte[256]; - public byte[] blockEntities = new byte[0]; + public List blockEntities = new ArrayList<>(); } }