Add block entity translators for Bedrock-only block entities (#478)

* Initial work on flower pots

* Flowers work in-game, not yet chunk load

* Don't overwrite my code before merge

* Finish up flower pots; add piston support on chunk load

* Clean up

* Remove debug line; update mappings
This commit is contained in:
Camotoy 2020-05-04 22:32:02 -04:00 committed by GitHub
parent fcf1949b28
commit b49004ddaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 306 additions and 5 deletions

View file

@ -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<BlockState> BANNER_COLORS = new Object2IntOpenHashMap<>();
private static final Object2ByteMap<BlockState> BED_COLORS = new Object2ByteOpenHashMap<>();
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
private static final Map<String, CompoundTag> FLOWER_POT_BLOCKS = new HashMap<>();
private static final Object2IntMap<BlockState> 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<BlockState> SKULL_VARIANTS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> SKULL_ROTATIONS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> 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<String> 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<String, CompoundTag> 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.

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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<Position, BlockState> blockEntityPositions = new HashMap<>();
// Temporarily stores compound tags of Bedrock-only block entities
ObjectArrayList<com.nukkitx.nbt.tag.CompoundTag> 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
}

@ -1 +1 @@
Subproject commit ddb62693f878a99f106a0d6ea16a92ec7c4c7cd0
Subproject commit 5b3a9ad1d2ef76105fb318e63126a096844b3195