Block entity performance improvements (#1481)

* BlockEntity performance improvements

* Use chunk cache if possible for block caching

* Get new block state from ViaVersion if block entity

* Add Javadoc for FlowerPotBlockEntityTranslator.isFlowerBlock

* Remove debug line

* Don't add all RequiresBlockState instances if cache chunks is enabled

* Double chest map get optimization

* Last changes

Co-authored-by: DoctorMacc <toy.fighter1@gmail.com>
This commit is contained in:
Tim203 2020-11-05 22:36:22 +01:00 committed by GitHub
parent 434a2e1500
commit c64d57439f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 362 additions and 404 deletions

View File

@ -43,16 +43,19 @@ import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
import org.geysermc.connector.utils.LanguageUtils;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.minecraft.Position;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
import java.io.InputStream;
import java.util.List;
public class GeyserSpigotWorldManager extends GeyserWorldManager {
/**
* The current client protocol version for ViaVersion usage.
*/
@ -99,8 +102,9 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
}
// Only load in the biomes that are present in this version of Minecraft
for (Biome enumBiome : Biome.values()) {
if (biomes.has(enumBiome.toString())) {
biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue());
JsonNode biome = biomes.get(enumBiome.toString());
if (biome != null) {
biomeToIdMap.put(enumBiome.ordinal(), biome.intValue());
} else {
GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() +
", defaulting to 0");
@ -127,30 +131,38 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) {
if (isViaVersion) {
return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true);
Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class);
return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z);
} else {
return BlockTranslator.AIR;
}
}
@SuppressWarnings("deprecation")
public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) {
if (isViaVersion) {
Block block = world.getBlockAt(x, y, z);
// Black magic that gets the old block state ID
int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
// Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId);
List<Pair<Integer, Protocol>> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getId());
for (int i = protocolList.size() - 1; i >= 0; i--) {
if (protocolList.get(i).getValue().getMappingData() == null) continue;
blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId);
public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
// Black magic that gets the old block state ID
int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
// Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId);
List<Pair<Integer, Protocol>> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getId());
// Translate block entity differences - some information was stored in block tags and not block states
if (storage.isWelcome(blockId)) { // No getOrDefault method
BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z));
if (data != null && data.getReplacement() != -1) {
blockId = data.getReplacement();
}
return blockId;
} else {
return BlockTranslator.AIR;
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getValue().getMappingData();
if (mappingData != null) {
blockId = mappingData.getNewBlockStateId(blockId);
}
}
return blockId;
}
@Override
@ -162,11 +174,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
return;
}
World world = bukkitPlayer.getWorld();
if (this.isLegacy) {
if (this.isLegacy) {
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class);
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true));
chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ));
}
}
}
@ -176,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ);
int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0);
int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR);
chunk.set(blockX, blockY, blockZ, id);
}
}

View File

@ -141,6 +141,7 @@ public class ItemFrameEntity extends Entity {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(bedrockPosition);
// TODO 1.16.100 set to BEDROCK_AIR
updateBlockPacket.setRuntimeId(0);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
@ -196,18 +197,6 @@ public class ItemFrameEntity extends Entity {
return session.getItemFrameCache().getOrDefault(position, -1);
}
/**
* Determines if the position contains an item frame.
* Does largely the same thing as getItemFrameEntityId, but for speed purposes is implemented separately,
* since every block destroy packet has to check for an item frame.
* @param position position of block.
* @param session GeyserSession.
* @return true if position contains item frame, false if not.
*/
public static boolean positionContainsItemFrame(GeyserSession session, Vector3i position) {
return session.getItemFrameCache().containsKey(position);
}
/**
* Force-remove from the position-to-ID map so it doesn't cause conflicts.
* @param session GeyserSession.

View File

@ -194,10 +194,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendUpstreamPacket(blockBreakPacket);
}
if (ItemFrameEntity.positionContainsItemFrame(session, packet.getBlockPosition()) &&
session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition())) != null) {
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()),
InteractAction.ATTACK, session.isSneaking());
long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition());
if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) {
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking());
session.sendDownstreamPacket(attackPacket);
break;
}

View File

@ -226,8 +226,14 @@ public class ItemRegistry {
* @return an item entry from the given java edition identifier
*/
public static ItemEntry getItemEntry(String javaIdentifier) {
return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values()
.stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null));
return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> {
for (ItemEntry entry : ITEM_ENTRIES.values()) {
if (entry.getJavaIdentifier().equals(key)) {
return entry;
}
}
return null;
});
}
/**

View File

@ -51,7 +51,6 @@ import java.util.*;
import java.util.stream.Collectors;
public abstract class ItemTranslator {
private static final Int2ObjectMap<ItemTranslator> ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>();
private static final List<NbtItemStackTranslator> NBT_TRANSLATORS;
@ -220,7 +219,7 @@ public abstract class ItemTranslator {
public abstract List<ItemEntry> getAppliedItems();
public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) {
Map<String, Object> javaValue = new HashMap<>();
NbtMapBuilder builder = NbtMap.builder();
if (tag.getValue() != null && !tag.getValue().isEmpty()) {
for (String str : tag.getValue().keySet()) {
com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str);
@ -228,11 +227,9 @@ public abstract class ItemTranslator {
if (translatedTag == null)
continue;
javaValue.put(javaTag.getName(), translatedTag);
builder.put(javaTag.getName(), translatedTag);
}
}
NbtMapBuilder builder = NbtMap.builder();
javaValue.forEach(builder::put);
return builder.build();
}

View File

@ -26,20 +26,16 @@
package org.geysermc.connector.network.translators.item.translators;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import java.util.ArrayList;
import java.util.HashMap;
@ -49,54 +45,13 @@ import java.util.stream.Collectors;
@ItemRemapper
public class BannerTranslator extends ItemTranslator {
private final List<ItemEntry> appliedItems;
public BannerTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList());
}
@Override
public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) {
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry);
ItemData itemData = super.translateToBedrock(itemStack, itemEntry);
CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag");
if (blockEntityTag.contains("Patterns")) {
ListTag patterns = blockEntityTag.get("Patterns");
NbtMapBuilder builder = itemData.getTag().toBuilder();
builder.put("Patterns", convertBannerPattern(patterns));
itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build());
}
return itemData;
}
@Override
public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) {
if (itemData.getTag() == null) return super.translateToJava(itemData, itemEntry);
ItemStack itemStack = super.translateToJava(itemData, itemEntry);
NbtMap nbtTag = itemData.getTag();
if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) {
List<NbtMap> patterns = nbtTag.getList("Patterns", NbtType.COMPOUND);
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(convertBannerPattern(patterns));
itemStack.getNbt().put(blockEntityTag);
}
return itemStack;
}
@Override
public List<ItemEntry> getAppliedItems() {
return appliedItems;
appliedItems = ItemRegistry.ITEM_ENTRIES.values()
.stream()
.filter(entry -> entry.getJavaIdentifier().endsWith("banner"))
.collect(Collectors.toList());
}
/**
@ -133,7 +88,6 @@ public class BannerTranslator extends ItemTranslator {
return NbtMap.builder()
.putInt("Color", 15 - (int) pattern.get("Color").getValue())
.putString("Pattern", (String) pattern.get("Pattern").getValue())
.putString("Pattern", patternName)
.build();
}
@ -147,8 +101,7 @@ public class BannerTranslator extends ItemTranslator {
public static ListTag convertBannerPattern(List<NbtMap> patterns) {
List<Tag> tagsList = new ArrayList<>();
for (Object patternTag : patterns) {
CompoundTag newPatternTag = getJavaBannerPattern((NbtMap) patternTag);
tagsList.add(newPatternTag);
tagsList.add(getJavaBannerPattern((NbtMap) patternTag));
}
return new ListTag("Patterns", tagsList);
@ -167,4 +120,51 @@ public class BannerTranslator extends ItemTranslator {
return new CompoundTag("", tags);
}
@Override
public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) {
if (itemStack.getNbt() == null) {
return super.translateToBedrock(itemStack, itemEntry);
}
ItemData itemData = super.translateToBedrock(itemStack, itemEntry);
CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag");
if (blockEntityTag.contains("Patterns")) {
ListTag patterns = blockEntityTag.get("Patterns");
NbtMapBuilder builder = itemData.getTag().toBuilder();
builder.put("Patterns", convertBannerPattern(patterns));
itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build());
}
return itemData;
}
@Override
public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) {
if (itemData.getTag() == null) {
return super.translateToJava(itemData, itemEntry);
}
ItemStack itemStack = super.translateToJava(itemData, itemEntry);
NbtMap nbtTag = itemData.getTag();
if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) {
List<NbtMap> patterns = nbtTag.getList("Patterns", NbtType.COMPOUND);
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(convertBannerPattern(patterns));
itemStack.getNbt().put(blockEntityTag);
}
return itemStack;
}
@Override
public List<ItemEntry> getAppliedItems() {
return appliedItems;
}
}

View File

@ -45,14 +45,13 @@ import org.geysermc.connector.utils.ChunkUtils;
@Translator(packet = ServerChunkDataPacket.class)
public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPacket> {
/**
* Determines if we should process non-full chunks
*/
private final boolean isCacheChunks;
private final boolean cacheChunks;
public JavaChunkDataTranslator() {
isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
}
@Override
@ -61,7 +60,7 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
}
if (packet.getColumn().getBiomeData() == null && !isCacheChunks) {
if (packet.getColumn().getBiomeData() == null && !cacheChunks) {
// Non-full chunk without chunk caching
session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off.");
return;

View File

@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdate
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -40,6 +41,11 @@ import org.geysermc.connector.utils.ChunkUtils;
@Translator(packet = ServerUpdateTileEntityPacket.class)
public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdateTileEntityPacket> {
private final boolean cacheChunks;
public JavaUpdateTileEntityTranslator() {
cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
}
@Override
public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) {
@ -48,16 +54,17 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdat
BlockEntityUtils.updateBlockEntity(session, null, packet.getPosition());
return;
}
BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id);
// If not null then the BlockState is used in BlockEntityTranslator.translateTag()
if (ChunkUtils.CACHED_BLOCK_ENTITIES.containsKey(packet.getPosition())) {
int blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.getOrDefault(packet.getPosition(), 0);
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(),
blockState), packet.getPosition());
ChunkUtils.CACHED_BLOCK_ENTITIES.remove(packet.getPosition(), blockState);
} else {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), 0), packet.getPosition());
}
// The Java block state is used in BlockEntityTranslator.translateTag() to make up for some inconsistencies
// between Java block states and Bedrock block entity data
int blockState = cacheChunks ?
// Cache chunks is enabled; use chunk cache
session.getConnector().getWorldManager().getBlockAt(session, packet.getPosition()) :
// Cache chunks is not enabled; use block entity cache
ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(packet.getPosition());
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), blockState), packet.getPosition());
// If block entity is command block, OP permission level is appropriate, player is in creative mode and the NBT is not empty
if (packet.getType() == UpdatedTileType.COMMAND_BLOCK && session.getOpPermissionLevel() >= 2 &&
session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) {

View File

@ -36,7 +36,6 @@ import java.util.Map;
* Used for block entities if the Java block state contains Bedrock block information.
*/
public class BlockStateValues {
private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap();
private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap();
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
@ -52,7 +51,8 @@ public class BlockStateValues {
/**
* Determines if the block state contains Bedrock block information
* @param entry The String to JsonNode map used in BlockTranslator
*
* @param entry The String to JsonNode map used in BlockTranslator
* @param javaBlockState the Java Block State of the block
*/
public static void storeBlockStateValues(Map.Entry<String, JsonNode> entry, int javaBlockState) {
@ -101,7 +101,7 @@ public class BlockStateValues {
}
JsonNode skullVariation = entry.getValue().get("variation");
if(skullVariation != null) {
if (skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
@ -124,10 +124,7 @@ public class BlockStateValues {
* @return Banner color integer or -1 if no color
*/
public static int getBannerColor(int state) {
if (BANNER_COLORS.containsKey(state)) {
return BANNER_COLORS.get(state);
}
return -1;
return BANNER_COLORS.getOrDefault(state, -1);
}
/**
@ -138,10 +135,7 @@ public class BlockStateValues {
* @return Bed color byte or -1 if no color
*/
public static byte getBedColor(int state) {
if (BED_COLORS.containsKey(state)) {
return BED_COLORS.get(state);
}
return -1;
return BED_COLORS.getOrDefault(state, (byte) -1);
}
/**
@ -157,6 +151,7 @@ public class BlockStateValues {
/**
* All double chest values are part of the block state in Java and part of the block entity tag in Bedrock.
* This gives the DoubleChestValue that can be calculated into the final tag.
*
* @return The map of all DoubleChestValues.
*/
public static Int2ObjectMap<DoubleChestValue> getDoubleChestValues() {
@ -165,6 +160,7 @@ public class BlockStateValues {
/**
* Get the Int2ObjectMap of flower pot block states to containing plant
*
* @return Int2ObjectMap of flower pot values
*/
public static Int2ObjectMap<String> getFlowerPotValues() {
@ -173,6 +169,7 @@ public class BlockStateValues {
/**
* Get the map of contained flower pot plants to Bedrock CompoundTag
*
* @return Map of flower pot blocks.
*/
public static Map<String, NbtMap> getFlowerPotBlocks() {
@ -182,18 +179,17 @@ public class BlockStateValues {
/**
* 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.
*
* @param state BlockState of the block
* @return note block note integer or -1 if not present
*/
public static int getNoteblockPitch(int state) {
if (NOTEBLOCK_PITCHES.containsKey(state)) {
return NOTEBLOCK_PITCHES.get(state);
}
return -1;
return NOTEBLOCK_PITCHES.getOrDefault(state, -1);
}
/**
* Get the Int2BooleanMap showing if a piston block state is extended or not.
*
* @return the Int2BooleanMap of piston extensions.
*/
public static Int2BooleanMap getPistonValues() {
@ -212,10 +208,7 @@ public class BlockStateValues {
* @return Skull variant byte or -1 if no variant
*/
public static byte getSkullVariant(int state) {
if (SKULL_VARIANTS.containsKey(state)) {
return SKULL_VARIANTS.get(state);
}
return -1;
return SKULL_VARIANTS.getOrDefault(state, (byte) -1);
}
/**
@ -226,10 +219,7 @@ public class BlockStateValues {
* @return Skull rotation value or -1 if no value
*/
public static byte getSkullRotation(int state) {
if (SKULL_ROTATIONS.containsKey(state)) {
return SKULL_ROTATIONS.get(state);
}
return -1;
return SKULL_ROTATIONS.getOrDefault(state, (byte) -1);
}
@ -241,9 +231,6 @@ public class BlockStateValues {
* @return Shulker direction value or -1 if no value
*/
public static byte getShulkerBoxDirection(int state) {
if (SHULKERBOX_DIRECTIONS.containsKey(state)) {
return SHULKERBOX_DIRECTIONS.get(state);
}
return -1;
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
}
}

View File

@ -27,39 +27,31 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "Banner", regex = "banner")
public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getBannerColor(blockState) != -1;
}
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
int bannerColor = BlockStateValues.getBannerColor(blockState);
if (bannerColor != -1) {
tags.put("Base", 15 - bannerColor);
builder.put("Base", 15 - bannerColor);
}
if (tag.contains("Patterns")) {
ListTag patterns = tag.get("Patterns");
tags.put("Patterns", BannerTranslator.convertBannerPattern(patterns));
builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns));
}
if (tag.contains("CustomName")) {
tags.put("CustomName", tag.get("CustomName").getValue());
builder.put("CustomName", tag.get("CustomName").getValue());
}
return tags;
}
}

View File

@ -26,27 +26,23 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "Bed", regex = "bed")
public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getBedColor(blockState) != -1;
}
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
byte bedcolor = BlockStateValues.getBedColor(blockState);
// Just in case...
if (bedcolor == -1) bedcolor = 0;
tags.put("color", bedcolor);
return tags;
if (bedcolor == -1) {
bedcolor = 0;
}
builder.put("color", bedcolor);
}
}

View File

@ -33,7 +33,6 @@ 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.
@ -49,7 +48,7 @@ public interface BedrockOnlyBlockEntity {
* @return Bedrock tag, or null if not a Bedrock-only Block Entity
*/
static NbtMap getTag(Vector3i position, int blockState) {
if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) {
if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) {
return FlowerPotBlockEntityTranslator.getTag(blockState, position);
} else if (PistonBlockEntityTranslator.isBlock(blockState)) {
return PistonBlockEntityTranslator.getTag(blockState, position);

View File

@ -41,10 +41,16 @@ import org.reflections.Reflections;
import java.util.HashMap;
import java.util.Map;
/**
* The class that all block entities (on both Java and Bedrock) should translate with
*/
public abstract class BlockEntityTranslator {
public static final Map<String, BlockEntityTranslator> BLOCK_ENTITY_TRANSLATORS = new HashMap<>();
public static ObjectArrayList<RequiresBlockState> REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>();
/**
* A list of all block entities that require the Java block state in order to fill out their block entity information.
* This list will be smaller with cache chunks on as we don't need to double-cache data
*/
public static final ObjectArrayList<RequiresBlockState> REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>();
/**
* Contains a list of irregular block entity name translations that can't be fit into the regex
@ -78,27 +84,33 @@ public abstract class BlockEntityTranslator {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName()));
}
}
boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
for (Class<?> clazz : ref.getSubTypesOf(RequiresBlockState.class)) {
GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName());
try {
REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance());
RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance();
if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) {
// Not needed to put this one in the map; cache chunks takes care of that for us
GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled.");
continue;
}
REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState);
} catch (InstantiationException | IllegalAccessException e) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName()));
}
}
}
public abstract Map<String, Object> translateTag(CompoundTag tag, int blockState);
public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState);
public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) {
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()));
int x = ((IntTag) tag.getValue().get("x")).getValue();
int y = ((IntTag) tag.getValue().get("y")).getValue();
int z = ((IntTag) tag.getValue().get("z")).getValue();
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder();
Map<String, Object> translatedTags = translateTag(tag, blockState);
translatedTags.forEach(tagBuilder::put);
translateTag(tagBuilder, tag, blockState);
return tagBuilder.build();
}

View File

@ -32,22 +32,16 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "Campfire", regex = "campfire")
public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
ListTag items = tag.get("Items");
int i = 1;
for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) {
tags.put("Item" + i, getItem((CompoundTag) itemTag));
builder.put("Item" + i, getItem((CompoundTag) itemTag));
i++;
}
return tags;
}
protected NbtMap getItem(CompoundTag tag) {

View File

@ -26,38 +26,33 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.utils.MessageUtils;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "CommandBlock", regex = "command_block")
public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> map = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
if (tag.size() < 5) {
return map; // These values aren't here
return; // These values aren't here
}
// Java infers from the block state, but Bedrock needs it in the tag
map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0));
builder.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0));
// Java and Bedrock values
map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
map.put("auto", ((ByteTag) tag.get("auto")).getValue());
map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue()));
map.put("powered", ((ByteTag) tag.get("powered")).getValue());
map.put("Command", ((StringTag) tag.get("Command")).getValue());
map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());
map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue());
map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue());
builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
builder.put("auto", ((ByteTag) tag.get("auto")).getValue());
builder.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue()));
builder.put("powered", ((ByteTag) tag.get("powered")).getValue());
builder.put("Command", ((StringTag) tag.get("Command")).getValue());
builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());
builder.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue());
builder.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue());
if (tag.get("LastExecution") != null) {
map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue());
builder.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue());
} else {
map.put("LastExecution", (long) 0);
builder.put("LastExecution", (long) 0);
}
return map;
}
@Override

View File

@ -33,15 +33,11 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.world.block.DoubleChestValue;
import org.geysermc.connector.utils.BlockEntityUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity
*/
@BlockEntity(name = "Chest", regex = "chest")
public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getDoubleChestValues().containsKey(blockState);
@ -51,44 +47,39 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl
public void updateBlock(GeyserSession session, int blockState, Vector3i position) {
CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ());
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder();
translateTag(javaTag, blockState).forEach(tagBuilder::put);
translateTag(tagBuilder, javaTag, blockState);
BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position);
}
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
if (BlockStateValues.getDoubleChestValues().containsKey(blockState)) {
DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState);
if (chestValues != null) {
int x = (int) tag.getValue().get("x").getValue();
int z = (int) tag.getValue().get("z").getValue();
// Calculate the position of the other chest based on the Java block state
if (chestValues.isFacingEast) {
if (chestValues.isDirectionPositive) {
// East
z = z + (chestValues.isLeft ? 1 : -1);
} else {
// West
z = z + (chestValues.isLeft ? -1 : 1);
}
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().getOrDefault(blockState, null);
if (chestValues != null) {
int x = (int) tag.getValue().get("x").getValue();
int z = (int) tag.getValue().get("z").getValue();
// Calculate the position of the other chest based on the Java block state
if (chestValues.isFacingEast) {
if (chestValues.isDirectionPositive) {
// East
z = z + (chestValues.isLeft ? 1 : -1);
} else {
if (chestValues.isDirectionPositive) {
// South
x = x + (chestValues.isLeft ? -1 : 1);
} else {
// North
x = x + (chestValues.isLeft ? 1 : -1);
}
// West
z = z + (chestValues.isLeft ? -1 : 1);
}
tags.put("pairx", x);
tags.put("pairz", z);
if (!chestValues.isLeft) {
tags.put("pairlead", (byte) 1);
} else {
if (chestValues.isDirectionPositive) {
// South
x = x + (chestValues.isLeft ? -1 : 1);
} else {
// North
x = x + (chestValues.isLeft ? 1 : -1);
}
}
builder.put("pairx", x);
builder.put("pairz", z);
if (!chestValues.isLeft) {
builder.put("pairlead", (byte) 1);
}
}
return tags;
}
}

View File

@ -26,16 +26,11 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import java.util.HashMap;
import java.util.Map;
import com.nukkitx.nbt.NbtMapBuilder;
@BlockEntity(name = "Empty", regex = "")
public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
return new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
}
}

View File

@ -28,21 +28,18 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@BlockEntity(name = "EndGateway", regex = "end_gateway")
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
tags.put("Age", (int) ((long) tag.get("Age").getValue()));
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
builder.put("Age", (int) ((long) tag.get("Age").getValue()));
// Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist
// Linked coordinates
IntList tagsList = new IntArrayList();
@ -50,8 +47,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
tagsList.add(getExitPortalCoordinate(tag, "X"));
tagsList.add(getExitPortalCoordinate(tag, "Y"));
tagsList.add(getExitPortalCoordinate(tag, "Z"));
tags.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList));
return tags;
builder.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList));
}
private int getExitPortalCoordinate(CompoundTag tag, String axis) {
@ -60,6 +56,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
LinkedHashMap<?, ?> compoundTag = (LinkedHashMap<?, ?>) tag.get("ExitPortal").getValue();
IntTag intTag = (IntTag) compoundTag.get(axis);
return intTag.getValue();
} return 0;
}
return 0;
}
}

View File

@ -35,30 +35,19 @@ 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(int blockState) {
return (BlockStateValues.getFlowerPotValues().containsKey(blockState));
}
@Override
public void updateBlock(GeyserSession session, int 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.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY);
session.sendUpstreamPacket(updateBlockPacket);
BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position);
/**
* @param blockState the Java block state of a potential flower pot block
* @return true if the block is a flower pot
*/
public static boolean isFlowerBlock(int blockState) {
return BlockStateValues.getFlowerPotValues().containsKey(blockState);
}
/**
* Get the Nukkit CompoundTag of the flower pot.
*
* @param blockState Java block state of flower pot.
* @param position Bedrock position of flower pot.
* @param position Bedrock position of flower pot.
* @return Bedrock tag of flower pot.
*/
public static NbtMap getTag(int blockState, Vector3i position) {
@ -80,4 +69,23 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R
}
return tagBuilder.build();
}
@Override
public boolean isBlock(int blockState) {
return isFlowerBlock(blockState);
}
@Override
public void updateBlock(GeyserSession session, int 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.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY);
session.sendUpstreamPacket(updateBlockPacket);
BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position);
}
}

View File

@ -27,21 +27,16 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import java.util.HashMap;
import java.util.Map;
import com.nukkitx.nbt.NbtMapBuilder;
@BlockEntity(name = "JigsawBlock", regex = "jigsaw")
public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> map = new HashMap<>();
map.put("joint", ((StringTag) tag.get("joint")).getValue());
map.put("name", ((StringTag) tag.get("name")).getValue());
map.put("target_pool", ((StringTag) tag.get("pool")).getValue());
map.put("final_state", ((StringTag) tag.get("final_state")).getValue());
map.put("target", ((StringTag) tag.get("target")).getValue());
return map;
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
builder.put("joint", ((StringTag) tag.get("joint")).getValue());
builder.put("name", ((StringTag) tag.get("name")).getValue());
builder.put("target_pool", ((StringTag) tag.get("pool")).getValue());
builder.put("final_state", ((StringTag) tag.get("final_state")).getValue());
builder.put("target", ((StringTag) tag.get("target")).getValue());
}
}

View File

@ -36,21 +36,20 @@ import org.geysermc.connector.utils.ChunkUtils;
* Does not implement BlockEntityTranslator because it's only a block entity in Bedrock
*/
public class NoteblockBlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getNoteblockPitch(blockState) != -1;
}
public static void translate(GeyserSession session, Position position) {
int blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.getOrDefault(position, 0);
int blockState = session.getConnector().getConfig().isCacheChunks() ?
session.getConnector().getWorldManager().getBlockAt(session, position) :
ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position);
BlockEventPacket blockEventPacket = new BlockEventPacket();
blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ()));
blockEventPacket.setEventType(0);
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
session.sendUpstreamPacket(blockEventPacket);
ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position);
}
}

View File

@ -34,9 +34,9 @@ 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.
*/
@ -46,8 +46,9 @@ public class PistonBlockEntityTranslator {
/**
* Calculates the Nukkit CompoundTag to send to the client on chunk
*
* @param blockState Java block state of block.
* @param position Bedrock position of piston.
* @param position Bedrock position of piston.
* @return Bedrock tag of piston.
*/
public static NbtMap getTag(int blockState, Vector3i position) {
@ -57,14 +58,13 @@ public class PistonBlockEntityTranslator {
.putInt("z", position.getZ())
.putByte("isMovable", (byte) 1)
.putString("id", "PistonArm");
if (BlockStateValues.getPistonValues().containsKey(blockState)) {
boolean extended = BlockStateValues.getPistonValues().get(blockState);
// 1f if extended, otherwise 0f
tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f);
// 1 if sticky, 0 if not
tagBuilder.putByte("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0));
}
boolean extended = BlockStateValues.getPistonValues().get(blockState);
// 1f if extended, otherwise 0f
tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f);
// 1 if sticky, 0 if not
tagBuilder.putByte("Sticky", (byte) ((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0));
return tagBuilder.build();
}
}

View File

@ -26,23 +26,18 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "ShulkerBox", regex = "shulker_box")
public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
byte direction = BlockStateValues.getShulkerBoxDirection(blockState);
// Just in case...
if (direction == -1) direction = 1;
tags.put("facing", direction);
return tags;
if (direction == -1) {
direction = 1;
}
builder.put("facing", direction);
}
}

View File

@ -27,51 +27,12 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.utils.MessageUtils;
import org.geysermc.connector.utils.SignUtils;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "Sign", regex = "sign")
public class SignBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
StringBuilder signText = new StringBuilder();
for(int i = 0; i < 4; i++) {
int currentLine = i+1;
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine));
// Check the character width on the sign to ensure there is no overflow that is usually hidden
// to Java Edition clients but will appear to Bedrock clients
int signWidth = 0;
StringBuilder finalSignLine = new StringBuilder();
for (char c : signLine.toCharArray()) {
signWidth += SignUtils.getCharacterWidth(c);
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
finalSignLine.append(c);
} else {
break;
}
}
// Java Edition 1.14 added the ability to change the text color of the whole sign using dye
if (tag.contains("Color")) {
signText.append(getBedrockSignColor(tag.get("Color").getValue().toString()));
}
signText.append(finalSignLine.toString());
signText.append("\n");
}
tags.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString())));
return tags;
}
/**
* Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code.
* <br>
@ -133,4 +94,36 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
return base;
}
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
StringBuilder signText = new StringBuilder();
for (int i = 0; i < 4; i++) {
int currentLine = i + 1;
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine));
// Check the character width on the sign to ensure there is no overflow that is usually hidden
// to Java Edition clients but will appear to Bedrock clients
int signWidth = 0;
StringBuilder finalSignLine = new StringBuilder();
for (char c : signLine.toCharArray()) {
signWidth += SignUtils.getCharacterWidth(c);
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
finalSignLine.append(c);
} else {
break;
}
}
// Java Edition 1.14 added the ability to change the text color of the whole sign using dye
if (tag.contains("Color")) {
signText.append(getBedrockSignColor(tag.get("Color").getValue().toString()));
}
signText.append(finalSignLine.toString());
signText.append("\n");
}
builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString())));
}
}

View File

@ -25,29 +25,26 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "Skull", regex = "skull")
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getSkullVariant(blockState) != -1;
}
@Override
public Map<String, Object> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
byte skullVariant = BlockStateValues.getSkullVariant(blockState);
float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f;
// Just in case...
if (skullVariant == -1) skullVariant = 0;
tags.put("Rotation", rotation);
tags.put("SkullType", skullVariant);
return tags;
if (skullVariant == -1) {
skullVariant = 0;
}
builder.put("Rotation", rotation);
builder.put("SkullType", skullVariant);
}
}

View File

@ -26,63 +26,58 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.entity.type.EntityType;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "MobSpawner", regex = "mob_spawner")
public class SpawnerBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> tags = new HashMap<>();
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
Tag current;
if (tag.get("MaxNearbyEntities") != null) {
tags.put("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue());
if ((current = tag.get("MaxNearbyEntities")) != null) {
builder.put("MaxNearbyEntities", current.getValue());
}
if (tag.get("RequiredPlayerRange") != null) {
tags.put("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue());
if ((current = tag.get("RequiredPlayerRange")) != null) {
builder.put("RequiredPlayerRange", current.getValue());
}
if (tag.get("SpawnCount") != null) {
tags.put("SpawnCount", (short) tag.get("SpawnCount").getValue());
if ((current = tag.get("SpawnCount")) != null) {
builder.put("SpawnCount", current.getValue());
}
if (tag.get("MaxSpawnDelay") != null) {
tags.put("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue());
if ((current = tag.get("MaxSpawnDelay")) != null) {
builder.put("MaxSpawnDelay", current.getValue());
}
if (tag.get("Delay") != null) {
tags.put("Delay", (short) tag.get("Delay").getValue());
if ((current = tag.get("Delay")) != null) {
builder.put("Delay", current.getValue());
}
if (tag.get("SpawnRange") != null) {
tags.put("SpawnRange", (short) tag.get("SpawnRange").getValue());
if ((current = tag.get("SpawnRange")) != null) {
builder.put("SpawnRange", current.getValue());
}
if (tag.get("MinSpawnDelay") != null) {
tags.put("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue());
if ((current = tag.get("MinSpawnDelay")) != null) {
builder.put("MinSpawnDelay", current.getValue());
}
if (tag.get("SpawnData") != null) {
CompoundTag spawnData = tag.get("SpawnData");
CompoundTag spawnData = tag.get("SpawnData");
if (spawnData != null) {
String entityID = (String) spawnData.get("id").getValue();
tags.put("EntityIdentifier", entityID);
builder.put("EntityIdentifier", entityID);
EntityType type = EntityType.getFromIdentifier(entityID);
if (type != null) {
tags.put("DisplayEntityWidth", type.getWidth());
tags.put("DisplayEntityHeight", type.getHeight());
tags.put("DisplayEntityScale", 1.0f);
builder.put("DisplayEntityWidth", type.getWidth());
builder.put("DisplayEntityHeight", type.getHeight());
builder.put("DisplayEntityScale", 1.0f);
}
}
tags.put("id", "MobSpawner");
tags.put("isMovable", (byte) 1);
return tags;
builder.put("id", "MobSpawner");
builder.put("isMovable", (byte) 1);
}
}

View File

@ -33,17 +33,17 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
public class BlockEntityUtils {
private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty");
public static String getBedrockBlockEntityId(String id) {
// These are the only exceptions when it comes to block entity ids
if (BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.containsKey(id)) {
return BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id);
String value = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id);
if (value != null) {
return value;
}
id = id.replace("minecraft:", "")
.replace("_", " ");
.replace("_", " ");
// Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already
String[] words;
if (!id.toUpperCase().equals(id)) { // Otherwise we get [S, K, U, L, L]
@ -60,11 +60,10 @@ public class BlockEntityUtils {
public static BlockEntityTranslator getBlockEntityTranslator(String name) {
BlockEntityTranslator blockEntityTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get(name);
if (blockEntityTranslator == null) {
return EMPTY_TRANSLATOR;
if (blockEntityTranslator != null) {
return blockEntityTranslator;
}
return blockEntityTranslator;
return EMPTY_TRANSLATOR;
}
public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) {

View File

@ -72,9 +72,9 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl
@UtilityClass
public class ChunkUtils {
/**
* Temporarily stores positions of BlockState values that are needed for certain block entities actively
* Temporarily stores positions of BlockState values that are needed for certain block entities actively.
* Not used if cache chunks is enabled
*/
public static final Object2IntMap<Position> CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>();
@ -300,11 +300,16 @@ public class ChunkUtils {
public static void updateBlock(GeyserSession session, int blockState, Vector3i position) {
// Checks for item frames so they aren't tripped up and removed
if (ItemFrameEntity.positionContainsItemFrame(session, position) && blockState == AIR) {
((ItemFrameEntity) session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position))).updateBlock(session);
return;
} else if (ItemFrameEntity.positionContainsItemFrame(session, position)) {
Entity entity = session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position));
long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position);
if (frameEntityId != -1) {
// TODO: Very occasionally the item frame doesn't sync up when destroyed
Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId);
if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it
((ItemFrameEntity) entity).updateBlock(session);
return;
}
// Otherwise the item frame is gone
if (entity != null) {
session.getEntityCache().removeEntity(entity, false);
} else {
@ -342,7 +347,10 @@ public class ChunkUtils {
((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position);
break;
}
CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState);
if (!session.getConnector().getConfig().isCacheChunks()) {
// Blocks aren't saved to a chunk cache; resort to this smaller cache
CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState);
}
break; //No block will be a part of two classes
}
}