Block entity rewrite (#382)

* Initial attempt

* Rewrite of the rewrite

* First working implementation

* Far better working implementation

* Clean up imports

* Remove commented code

* Cleanup code; change things

* Remove unused imports

* Cleanup code

* Add licenses; add comment

* More cleanup

* Clarifications

* It complained about a JavaDoc comment

* Update access permissions

* Switch from reflections to iteration over BlockEntityTranslators
This commit is contained in:
Camotoy 2020-04-21 01:32:32 -04:00 committed by GitHub
parent 3f8d5cc1c5
commit 94ecb2c6c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 402 additions and 259 deletions

View File

@ -116,11 +116,18 @@ public class Translators {
} }
private static void registerBlockEntityTranslators() { private static void registerBlockEntityTranslators() {
blockEntityTranslators.put("Empty", new EmptyBlockEntityTranslator()); Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity");
blockEntityTranslators.put("Sign", new SignBlockEntityTranslator());
blockEntityTranslators.put("Campfire", new CampfireBlockEntityTranslator()); for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
blockEntityTranslators.put("Banner", new BannerBlockEntityTranslator());
blockEntityTranslators.put("EndGateway", new EndGatewayBlockEntityTranslator()); GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName());
try {
blockEntityTranslators.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + ".");
}
}
} }
private static void registerInventoryTranslators() { private static void registerInventoryTranslators() {

View File

@ -0,0 +1,127 @@
/*
* 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.block;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
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.Map;
/**
* Used for block entities if the Java block state contains Bedrock block information.
*/
public class BlockStateValues {
private static final Object2IntMap<BlockState> BANNER_COLORS = new Object2IntOpenHashMap<>();
private static final Object2ByteMap<BlockState> BED_COLORS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> SKULL_VARIANTS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> SKULL_ROTATIONS = new Object2ByteOpenHashMap<>();
/**
* Determines if the block state contains Bedrock block information
* @param entry The String -> JsonNode map used in BlockTranslator
* @param javaBlockState the Java Block State of the block
*/
public static void storeBlockStateValues(Map.Entry<String, JsonNode> entry, BlockState javaBlockState) {
JsonNode bannerColor = entry.getValue().get("banner_color");
if (bannerColor != null) {
BlockStateValues.BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
return; // There will never be a banner color and a skull variant
}
JsonNode bedColor = entry.getValue().get("bed_color");
if (bedColor != null) {
BlockStateValues.BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
return;
}
JsonNode skullVariation = entry.getValue().get("variation");
if(skullVariation != null) {
BlockStateValues.SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
JsonNode skullRotation = entry.getValue().get("skull_rotation");
if (skullRotation != null) {
BlockStateValues.SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
}
}
/**
* Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives an integer color that Bedrock can use.
* @param state BlockState of the block
* @return banner color integer or -1 if no color
*/
public static int getBannerColor(BlockState state) {
if (BANNER_COLORS.containsKey(state)) {
return BANNER_COLORS.getInt(state);
}
return -1;
}
/**
* Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag.
* @param state BlockState of the block
* @return bed color byte or -1 if no color
*/
public static byte getBedColor(BlockState state) {
if (BED_COLORS.containsKey(state)) {
return BED_COLORS.getByte(state);
}
return -1;
}
/**
* 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.
* @param state BlockState of the block
* @return skull variant byte or -1 if no variant
*/
public static byte getSkullVariant(BlockState state) {
if (SKULL_VARIANTS.containsKey(state)) {
return SKULL_VARIANTS.getByte(state);
}
return -1;
}
/**
*
* @param state BlockState of the block
* @return skull rotation value or -1 if no value
*/
public static byte getSkullRotation(BlockState state) {
if (SKULL_ROTATIONS.containsKey(state)) {
return SKULL_ROTATIONS.getByte(state);
}
return -1;
}
}

View File

@ -42,12 +42,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
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.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.block.entity.BlockEntity;
import org.geysermc.connector.utils.Toolbox; import org.geysermc.connector.utils.Toolbox;
import org.reflections.Reflections;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.*;
@ -66,9 +66,6 @@ public class BlockTranslator {
public static final int CARPET = 171; public static final int CARPET = 171;
private static final Map<BlockState, String> JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>(); private static final Map<BlockState, String> JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>();
private static final Object2ByteMap<BlockState> BED_COLORS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> SKULL_VARIANTS = new Object2ByteOpenHashMap<>();
private static final Object2ByteMap<BlockState> SKULL_ROTATIONS = new Object2ByteOpenHashMap<>();
public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap();
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
@ -110,6 +107,9 @@ public class BlockTranslator {
addedStatesMap.defaultReturnValue(-1); addedStatesMap.defaultReturnValue(-1);
List<CompoundTag> paletteList = new ArrayList<>(); List<CompoundTag> paletteList = new ArrayList<>();
Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity");
ref.getTypesAnnotatedWith(BlockEntity.class);
int waterRuntimeId = -1; int waterRuntimeId = -1;
int javaRuntimeId = -1; int javaRuntimeId = -1;
int bedrockRuntimeId = 0; int bedrockRuntimeId = 0;
@ -145,28 +145,19 @@ public class BlockTranslator {
JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState); JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState);
if (javaId.contains("sign[")) { // Used for adding all "special" Java block states to block state map
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaBlockState, javaId); String identifier;
String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText();
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
identifier = clazz.getAnnotation(BlockEntity.class).regex();
// Endswith, or else the block bedrock gets picked up for bed
if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) {
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaBlockState, clazz.getAnnotation(BlockEntity.class).name());
break;
}
} }
BlockStateValues.storeBlockStateValues(entry, javaBlockState);
JsonNode skullVariation = entry.getValue().get("variation");
if(skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
JsonNode skullRotation = entry.getValue().get("skull_rotation");
if (skullRotation != null) {
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
}
// If the Java ID is bed, signal that it needs a tag to show color
// The color is in the namespace ID in Java Edition but it's a tag in Bedrock.
JsonNode bedColor = entry.getValue().get("bed_color");
if (bedColor != null) {
// Converting to byte because the final tag value is a byte. bedColor.binaryValue() returns an array
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
}
if ("minecraft:water[level=0]".equals(javaId)) { if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = bedrockRuntimeId; waterRuntimeId = bedrockRuntimeId;
@ -186,7 +177,7 @@ public class BlockTranslator {
addedStatesMap.put(blockTag, bedrockRuntimeId); addedStatesMap.put(blockTag, bedrockRuntimeId);
paletteList.add(runtimeTag); paletteList.add(runtimeTag);
} else { } else {
int duplicateRuntimeId = addedStatesMap.get(blockTag); int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1);
if (duplicateRuntimeId == -1) { if (duplicateRuntimeId == -1) {
GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!");
} else { } else {
@ -274,27 +265,6 @@ public class BlockTranslator {
return WATERLOGGED.contains(state.getId()); return WATERLOGGED.contains(state.getId());
} }
public static byte getBedColor(BlockState state) {
if (BED_COLORS.containsKey(state)) {
return BED_COLORS.getByte(state);
}
return -1;
}
public static byte getSkullVariant(BlockState state) {
if (SKULL_VARIANTS.containsKey(state)) {
return SKULL_VARIANTS.getByte(state);
}
return -1;
}
public static byte getSkullRotation(BlockState state) {
if (SKULL_ROTATIONS.containsKey(state)) {
return SKULL_ROTATIONS.getByte(state);
}
return -1;
}
public static BlockState getJavaWaterloggedState(int bedrockId) { public static BlockState getJavaWaterloggedState(int bedrockId) {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId);
} }

View File

@ -25,20 +25,33 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
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.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.IntTag;
import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.nbt.tag.StringTag;
import com.nukkitx.nbt.tag.Tag; import com.nukkitx.nbt.tag.Tag;
import org.geysermc.connector.network.translators.block.BlockStateValues;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BannerBlockEntityTranslator extends BlockEntityTranslator { @BlockEntity(name = "Banner", delay = false, regex = "banner")
public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override @Override
public List<Tag<?>> translateTag(CompoundTag tag) { public boolean isBlock(BlockState blockState) {
return BlockStateValues.getBannerColor(blockState) != -1;
}
@Override
public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>(); List<Tag<?>> tags = new ArrayList<>();
int bannerColor = BlockStateValues.getBannerColor(blockState);
if (bannerColor != -1) {
tags.add(new IntTag("Base", 15 - bannerColor));
}
ListTag patterns = tag.get("Patterns"); ListTag patterns = tag.get("Patterns");
List<com.nukkitx.nbt.tag.CompoundTag> tagsList = new ArrayList<>(); List<com.nukkitx.nbt.tag.CompoundTag> tagsList = new ArrayList<>();
if (tag.contains("Patterns")) { if (tag.contains("Patterns")) {
@ -71,7 +84,7 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator {
protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) { protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) {
return CompoundTagBuilder.builder() return CompoundTagBuilder.builder()
.intTag("Color", (int) pattern.get("Color").getValue()) .intTag("Color", 15 - (int) pattern.get("Color").getValue())
.stringTag("Pattern", (String) pattern.get("Pattern").getValue()) .stringTag("Pattern", (String) pattern.get("Pattern").getValue())
.buildRootTag(); .buildRootTag();
} }

View File

@ -25,42 +25,43 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.nukkitx.math.vector.Vector3i; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
import org.geysermc.connector.network.session.GeyserSession; import com.nukkitx.nbt.tag.ByteTag;
import org.geysermc.connector.network.translators.block.BlockTranslator; import com.nukkitx.nbt.tag.Tag;
import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.network.translators.block.BlockStateValues;
import java.util.concurrent.TimeUnit; import java.util.ArrayList;
import java.util.List;
public class BedBlockEntityTranslator { @BlockEntity(name = "Bed", delay = false, regex = "bed")
public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
public static void checkForBedColor(GeyserSession session, BlockState blockState, Vector3i position) { @Override
byte bedcolor = BlockTranslator.getBedColor(blockState); public boolean isBlock(BlockState blockState) {
// If Bed Color is not -1 then it is indeed a bed with a color. return BlockStateValues.getBedColor(blockState) != -1;
if (bedcolor > -1) {
Position pos = new Position(position.getX(), position.getY(), position.getZ());
com.nukkitx.nbt.tag.CompoundTag finalbedTag = getBedTag(bedcolor, pos);
// Delay needed, otherwise newly placed beds will not get their color
// Delay is not needed for beds already placed on login
session.getConnector().getGeneralThreadPool().schedule(() ->
BlockEntityUtils.updateBlockEntity(session, finalbedTag, pos),
500,
TimeUnit.MILLISECONDS
);
}
} }
public static com.nukkitx.nbt.tag.CompoundTag getBedTag(byte bedcolor, Position pos) { @Override
CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
.intTag("x", pos.getX()) List<Tag<?>> tags = new ArrayList<>();
.intTag("y", pos.getY()) byte bedcolor = BlockStateValues.getBedColor(blockState);
.intTag("z", pos.getZ()) // Just in case...
.stringTag("id", "Bed"); if (bedcolor == -1) bedcolor = 0;
tagBuilder.byteTag("color", bedcolor); tags.add(new ByteTag("color", bedcolor));
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return null;
}
@Override
public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder();
tagBuilder.byteTag("color", (byte) 0);
return tagBuilder.buildRootTag(); return tagBuilder.buildRootTag();
} }
} }

View File

@ -0,0 +1,45 @@
/*
* 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.block.entity;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.RUNTIME)
public @interface BlockEntity {
/**
* Whether to delay the sending of the block entity
*/
boolean delay();
/**
* The block entity name
*/
String name();
/**
* The search term used in BlockTranslator
*/
String regex();
}

View File

@ -25,32 +25,32 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
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.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.Tag; import com.nukkitx.nbt.tag.Tag;
import lombok.AllArgsConstructor;
import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.BlockEntityUtils;
import java.util.List; import java.util.List;
public abstract class BlockEntityTranslator { public abstract class BlockEntityTranslator {
public abstract List<Tag<?>> translateTag(CompoundTag tag); public abstract List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState);
public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z);
public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z); public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z);
public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(String id, CompoundTag tag) { public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(String id, CompoundTag tag, BlockState blockState) {
int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue()));
int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue()));
int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue()));
CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder();
translateTag(tag).forEach(tagBuilder::tag); translateTag(tag, blockState).forEach(tagBuilder::tag);
return tagBuilder.buildRootTag(); return tagBuilder.buildRootTag();
} }

View File

@ -25,6 +25,7 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
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.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
@ -37,10 +38,11 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@BlockEntity(name = "Campfire", delay = false, regex = "campfire")
public class CampfireBlockEntityTranslator extends BlockEntityTranslator { public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
@Override @Override
public List<Tag<?>> translateTag(CompoundTag tag) { public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>(); List<Tag<?>> tags = new ArrayList<>();
ListTag items = tag.get("Items"); ListTag items = tag.get("Items");
int i = 1; int i = 1;

View File

@ -1,80 +0,0 @@
/*
* 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.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.Tag;
import org.geysermc.connector.network.translators.Translators;
import org.geysermc.connector.network.translators.item.ItemEntry;
import java.util.ArrayList;
import java.util.List;
public class ContainerBlockEntityTranslator extends BlockEntityTranslator {
@Override
public List<Tag<?>> translateTag(CompoundTag tag) {
List<Tag<?>> tags = new ArrayList<>();
ListTag items = tag.get("Items");
List<com.nukkitx.nbt.tag.CompoundTag> tagsList = new ArrayList<>();
for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) {
tagsList.add(getItem((CompoundTag) itemTag));
}
com.nukkitx.nbt.tag.ListTag<com.nukkitx.nbt.tag.CompoundTag> bedrockItems =
new com.nukkitx.nbt.tag.ListTag<>("Items", com.nukkitx.nbt.tag.CompoundTag.class, tagsList);
tags.add(bedrockItems);
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
tag.put(new ListTag("Items"));
return tag;
}
@Override
public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder();
tagBuilder.listTag("Items", com.nukkitx.nbt.tag.CompoundTag.class, new ArrayList<>());
return tagBuilder.buildRootTag();
}
protected com.nukkitx.nbt.tag.CompoundTag getItem(CompoundTag tag) {
ItemEntry entry = Translators.getItemTranslator().getItemEntry((String) tag.get("id").getValue());
CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder()
.shortTag("id", (short) entry.getBedrockId())
.byteTag("Count", (byte) tag.get("Count").getValue())
.shortTag("Damage", (short) entry.getBedrockData())
.byteTag("Slot", (byte) tag.get("Slot").getValue())
.tag(CompoundTagBuilder.builder().build("tag"));
return tagBuilder.buildRootTag();
}
}

View File

@ -25,16 +25,18 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
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.CompoundTag;
import com.nukkitx.nbt.tag.Tag; import com.nukkitx.nbt.tag.Tag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@BlockEntity(name = "Empty", delay = false, regex = "")
public class EmptyBlockEntityTranslator extends BlockEntityTranslator { public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
@Override @Override
public List<Tag<?>> translateTag(CompoundTag tag) { public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -25,6 +25,7 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
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.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag; import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
@ -35,9 +36,11 @@ import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@BlockEntity(name = "EndGateway", delay = true, regex = "end_gateway")
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
@Override @Override
public List<Tag<?>> translateTag(CompoundTag tag) { public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>(); List<Tag<?>> tags = new ArrayList<>();
tags.add(new IntTag("Age", (int) (long) tag.get("Age").getValue())); tags.add(new IntTag("Age", (int) (long) tag.get("Age").getValue()));
// Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist

View File

@ -0,0 +1,42 @@
/*
* 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.block.entity;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
/**
* Implemented in block entities if their Java block state is required for additional values in Bedrock
*/
public interface RequiresBlockState {
/**
* Determines if block is part of class
* @param blockState BlockState to be compared
* @return true if part of the class
*/
boolean isBlock(BlockState blockState);
}

View File

@ -25,21 +25,22 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.github.steveice10.mc.protocol.data.message.Message; import com.github.steveice10.mc.protocol.data.message.Message;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.nbt.tag.StringTag;
import com.nukkitx.nbt.tag.Tag; import com.nukkitx.nbt.tag.Tag;
import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.MessageUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@BlockEntity(name = "Sign", delay = true, regex = "sign")
public class SignBlockEntityTranslator extends BlockEntityTranslator { public class SignBlockEntityTranslator extends BlockEntityTranslator {
@Override @Override
public List<Tag<?>> translateTag(CompoundTag tag) { public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>(); List<Tag<?>> tags = new ArrayList<>();
String line1 = getOrDefault(tag.getValue().get("Text1"), ""); String line1 = getOrDefault(tag.getValue().get("Text1"), "");

View File

@ -25,43 +25,47 @@
package org.geysermc.connector.network.translators.block.entity; package org.geysermc.connector.network.translators.block.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; 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.CompoundTagBuilder;
import com.nukkitx.nbt.tag.ByteTag;
import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.CompoundTag;
import org.geysermc.connector.network.session.GeyserSession; import com.nukkitx.nbt.tag.FloatTag;
import org.geysermc.connector.network.translators.block.BlockTranslator; import com.nukkitx.nbt.tag.Tag;
import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.network.translators.block.BlockStateValues;
import java.util.concurrent.TimeUnit; import java.util.ArrayList;
import java.util.List;
public class SkullBlockEntityTranslator { @BlockEntity(name = "Skull", delay = false, regex = "skull")
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
public static void checkForSkullVariant(GeyserSession session, BlockState blockState, Vector3i position) { @Override
byte skullVariant = BlockTranslator.getSkullVariant(blockState); public boolean isBlock(BlockState blockState) {
byte rotation = BlockTranslator.getSkullRotation(blockState); return BlockStateValues.getSkullVariant(blockState) != -1;
if (skullVariant > -1) {
Position pos = new Position(position.getX(), position.getY(), position.getZ());
CompoundTag finalSkullTag = getSkullTag(skullVariant, pos, rotation);
// Delay needed, otherwise newly placed skulls will not appear
// Delay is not needed for skulls already placed on login
session.getConnector().getGeneralThreadPool().schedule(() ->
BlockEntityUtils.updateBlockEntity(session, finalSkullTag, pos),
500,
TimeUnit.MILLISECONDS
);
}
} }
public static CompoundTag getSkullTag(byte skullvariant, Position pos, byte rotation) { @Override
CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() public List<Tag<?>> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) {
.intTag("x", pos.getX()) List<Tag<?>> tags = new ArrayList<>();
.intTag("y", pos.getY()) byte skullVariant = BlockStateValues.getSkullVariant(blockState);
.intTag("z", pos.getZ()) float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f;
.stringTag("id", "Skull") // Just in case...
.floatTag("Rotation", rotation * 22.5f); if (skullVariant == -1) skullVariant = 0;
tagBuilder.byteTag("SkullType", skullvariant); tags.add(new FloatTag("Rotation", rotation));
tags.add(new ByteTag("SkullType", skullVariant));
return tags;
}
@Override
public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return null;
}
@Override
public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder();
tagBuilder.floatTag("Rotation", 0);
tagBuilder.byteTag("SkullType", (byte) 0);
return tagBuilder.buildRootTag(); return tagBuilder.buildRootTag();
} }
} }

View File

@ -85,7 +85,7 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer()); ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer());
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream); NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream);
for (CompoundTag blockEntity : chunkData.blockEntities) { for (CompoundTag blockEntity : chunkData.getBlockEntities()) {
nbtStream.write(blockEntity); nbtStream.write(blockEntity);
} }
@ -102,31 +102,15 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
levelChunkPacket.setData(payload); levelChunkPacket.setData(payload);
session.getUpstream().sendPacket(levelChunkPacket); session.getUpstream().sendPacket(levelChunkPacket);
// Signs have to be sent after the chunk since in later versions they aren't included in the block entities // Some block entities need to be loaded in later or else text doesn't show (signs) or they crash the game (end gateway blocks)
for (Object2IntMap.Entry<CompoundTag> blockEntityEntry : chunkData.signs.object2IntEntrySet()) { for (Object2IntMap.Entry<CompoundTag> blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) {
int x = blockEntityEntry.getKey().getInt("x"); int x = blockEntityEntry.getKey().getInt("x");
int y = blockEntityEntry.getKey().getInt("y"); int y = blockEntityEntry.getKey().getInt("y");
int z = blockEntityEntry.getKey().getInt("z"); int z = blockEntityEntry.getKey().getInt("z");
ChunkUtils.updateBlock(session, new BlockState(blockEntityEntry.getIntValue()), new Position(x, y, z)); ChunkUtils.updateBlock(session, new BlockState(blockEntityEntry.getIntValue()), new Position(x, y, z));
} }
chunkData.getLoadBlockEntitiesLater().clear();
for (Object2IntMap.Entry<CompoundTag> blockEntityEntry : chunkData.gateways.object2IntEntrySet()) {
int x = blockEntityEntry.getKey().getInt("x");
int y = blockEntityEntry.getKey().getInt("y");
int z = blockEntityEntry.getKey().getInt("z");
ChunkUtils.updateBlock(session, new BlockState(blockEntityEntry.getIntValue()), new Position(x, y, z));
}
for (Map.Entry<Position, BlockState> blockEntityEntry: chunkData.beds.entrySet()) {
ChunkUtils.updateBlock(session, blockEntityEntry.getValue(), blockEntityEntry.getKey());
}
for (Map.Entry<Position, BlockState> blockEntityEntry: chunkData.skulls.entrySet()) {
ChunkUtils.updateBlock(session, blockEntityEntry.getValue(), blockEntityEntry.getKey());
}
chunkData.signs.clear();
chunkData.gateways.clear();
chunkData.beds.clear();
chunkData.skulls.clear();
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} }

View File

@ -30,27 +30,35 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdate
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.block.entity.BlockEntity;
import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator;
import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.BlockEntityUtils;
import org.geysermc.connector.utils.ChunkUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Translator(packet = ServerUpdateTileEntityPacket.class) @Translator(packet = ServerUpdateTileEntityPacket.class)
public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdateTileEntityPacket> { public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdateTileEntityPacket> {
// This should be modified if sign text is not showing up
private static final int DELAY = 500;
@Override @Override
public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) {
String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name());
BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id); BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id);
if (id.equalsIgnoreCase("Sign")) { // If not null then the BlockState is used in BlockEntityTranslator.translateTag()
if (ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition()) != null) {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(),
ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition())), packet.getPosition());
ChunkUtils.CACHED_BLOCK_ENTITIES.remove(packet.getPosition());
} else if (translator.getClass().getAnnotation(BlockEntity.class).delay()) {
// Delay so chunks can finish sending // Delay so chunks can finish sending
session.getConnector().getGeneralThreadPool().schedule(() -> session.getConnector().getGeneralThreadPool().schedule(() ->
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag("Sign", packet.getNbt()), packet.getPosition()), BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()),
5, DELAY, TimeUnit.MILLISECONDS);
TimeUnit.SECONDS
);
} else { } else {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt()), packet.getPosition()); BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition());
} }
} }
} }

View File

@ -39,14 +39,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.block.entity.*;
import org.geysermc.connector.network.translators.block.entity.SkullBlockEntityTranslator;
import org.geysermc.connector.world.chunk.ChunkPosition;
import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.Translators;
import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.network.translators.block.entity.BedBlockEntityTranslator; import org.geysermc.connector.world.chunk.ChunkPosition;
import org.geysermc.connector.world.chunk.ChunkSection; import org.geysermc.connector.world.chunk.ChunkSection;
import java.util.HashMap; import java.util.HashMap;
@ -56,12 +55,20 @@ import static org.geysermc.connector.network.translators.block.BlockTranslator.B
public class ChunkUtils { public class ChunkUtils {
/**
* Temporarily stores positions of BlockState values that are needed for certain block entities actively
*/
public static final Map<Position, BlockState> CACHED_BLOCK_ENTITIES = new HashMap<>();
public static ChunkData translateToBedrock(Column column) { public static ChunkData translateToBedrock(Column column) {
ChunkData chunkData = new ChunkData(); ChunkData chunkData = new ChunkData();
Chunk[] chunks = column.getChunks(); Chunk[] chunks = column.getChunks();
chunkData.sections = new ChunkSection[chunks.length]; chunkData.sections = new ChunkSection[chunks.length];
CompoundTag[] blockEntities = column.getTileEntities(); CompoundTag[] blockEntities = column.getTileEntities();
// Temporarily stores positions of BlockState values per chunk load
Map<Position, BlockState> blockEntityPositions = new HashMap<>();
for (int chunkY = 0; chunkY < chunks.length; chunkY++) { for (int chunkY = 0; chunkY < chunks.length; chunkY++) {
chunkData.sections[chunkY] = new ChunkSection(); chunkData.sections[chunkY] = new ChunkSection();
Chunk chunk = chunks[chunkY]; Chunk chunk = chunks[chunkY];
@ -76,21 +83,19 @@ public class ChunkUtils {
BlockState blockState = chunk.get(x, y, z); BlockState blockState = chunk.get(x, y, z);
int id = BlockTranslator.getBedrockBlockId(blockState); int id = BlockTranslator.getBedrockBlockId(blockState);
if (BlockTranslator.getBlockEntityString(blockState) != null && BlockTranslator.getBlockEntityString(blockState).contains("sign[")) { // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently
if (BlockTranslator.getBlockEntityString(blockState) != null) {
// Get the block entity translator
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(BlockTranslator.getBlockEntityString(blockState));
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
chunkData.signs.put(Translators.getBlockEntityTranslators().get("Sign").getDefaultBedrockTag("Sign", pos.getX(), pos.getY(), pos.getZ()), blockState.getId()); blockEntityPositions.put(pos, blockState);
} else if (BlockTranslator.getBlockEntityString(blockState) != null && BlockTranslator.getBlockEntityString(blockState).contains("end_gateway")) { // If there is a delay required for the block, allow it.
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); if (blockEntityTranslator.getClass().getAnnotation(BlockEntity.class).delay()) {
chunkData.gateways.put(Translators.getBlockEntityTranslators().get("EndGateway").getDefaultBedrockTag("EndGateway", pos.getX(), pos.getY(), pos.getZ()), blockState.getId()); chunkData.loadBlockEntitiesLater.put(blockEntityTranslator.getDefaultBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockTranslator.getBlockEntityString(blockState)),
} else if (BlockTranslator.getBedColor(blockState) > -1) { pos.getX(), pos.getY(), pos.getZ()), blockState.getId());
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); } else {
// Beds need to be updated separately to add the bed color tag section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id);
// Previously this was done by matching block state but this resulted in only one bed per color+orientation showing }
chunkData.beds.put(pos, blockState);
} else if (BlockTranslator.getSkullVariant(blockState) > 0) {
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
//Doing the same stuff as beds
chunkData.skulls.put(pos, blockState);
} else { } else {
section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id);
} }
@ -101,6 +106,7 @@ public class ChunkUtils {
} }
} }
} }
} }
com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length]; com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length];
@ -116,7 +122,8 @@ public class ChunkUtils {
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag); BlockState blockState = blockEntityPositions.get(new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()));
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState);
} }
chunkData.blockEntities = bedrockBlockEntities; chunkData.blockEntities = bedrockBlockEntities;
@ -162,10 +169,18 @@ public class ChunkUtils {
} }
session.getUpstream().sendPacket(waterPacket); session.getUpstream().sendPacket(waterPacket);
// Since Java stores bed colors as part of the namespaced ID and Bedrock stores it as a tag // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag
// This is the only place I could find that interacts with the Java block state and block updates // This is the only place I could find that interacts with the Java block state and block updates
BedBlockEntityTranslator.checkForBedColor(session, blockState, position); // Iterates through all block entity translators and determines if the block state needs to be saved
SkullBlockEntityTranslator.checkForSkullVariant(session, blockState, position); for (Map.Entry<String, BlockEntityTranslator> entry : Translators.getBlockEntityTranslators().entrySet()) {
if (entry.getValue() instanceof RequiresBlockState) {
RequiresBlockState requiresBlockState = (RequiresBlockState) entry.getValue();
if (requiresBlockState.isBlock(blockState)) {
CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState);
break; //No block will be a part of two classes
}
}
}
} }
public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) { public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) {
@ -196,10 +211,9 @@ public class ChunkUtils {
public static final class ChunkData { public static final class ChunkData {
public ChunkSection[] sections; public ChunkSection[] sections;
public com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0]; @Getter
public Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> signs = new Object2IntOpenHashMap<>(); private com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0];
public Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> gateways = new Object2IntOpenHashMap<>(); @Getter
public Map<Position, BlockState> beds = new HashMap<>(); private Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> loadBlockEntitiesLater = new Object2IntOpenHashMap<>();
public Map<Position, BlockState> skulls = new HashMap<>();
} }
} }