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

@ -91,15 +91,15 @@ public class Translators {
if (Packet.class.isAssignableFrom(packet)) {
Class<? extends Packet> targetPacket = (Class<? extends Packet>) packet;
PacketTranslator<? extends Packet> translator = (PacketTranslator<? extends Packet>) clazz.newInstance();
Registry.registerJava(targetPacket, translator);
} else if (BedrockPacket.class.isAssignableFrom(packet)) {
Class<? extends BedrockPacket> targetPacket = (Class<? extends BedrockPacket>) packet;
PacketTranslator<? extends BedrockPacket> translator = (PacketTranslator<? extends BedrockPacket>) clazz.newInstance();
Registry.registerBedrock(targetPacket, translator);
} else {
GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet.");
}
@ -116,11 +116,18 @@ public class Translators {
}
private static void registerBlockEntityTranslators() {
blockEntityTranslators.put("Empty", new EmptyBlockEntityTranslator());
blockEntityTranslators.put("Sign", new SignBlockEntityTranslator());
blockEntityTranslators.put("Campfire", new CampfireBlockEntityTranslator());
blockEntityTranslators.put("Banner", new BannerBlockEntityTranslator());
blockEntityTranslators.put("EndGateway", new EndGatewayBlockEntityTranslator());
Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity");
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
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() {

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.IntOpenHashSet;
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.Object2IntOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.block.entity.BlockEntity;
import org.geysermc.connector.utils.Toolbox;
import org.reflections.Reflections;
import java.io.InputStream;
import java.util.*;
@ -66,9 +66,6 @@ public class BlockTranslator {
public static final int CARPET = 171;
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 Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
@ -110,6 +107,9 @@ public class BlockTranslator {
addedStatesMap.defaultReturnValue(-1);
List<CompoundTag> paletteList = new ArrayList<>();
Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity");
ref.getTypesAnnotatedWith(BlockEntity.class);
int waterRuntimeId = -1;
int javaRuntimeId = -1;
int bedrockRuntimeId = 0;
@ -145,28 +145,19 @@ public class BlockTranslator {
JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState);
if (javaId.contains("sign[")) {
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaBlockState, javaId);
// Used for adding all "special" Java block states to block state map
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;
}
}
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());
}
BlockStateValues.storeBlockStateValues(entry, javaBlockState);
if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = bedrockRuntimeId;
@ -186,7 +177,7 @@ public class BlockTranslator {
addedStatesMap.put(blockTag, bedrockRuntimeId);
paletteList.add(runtimeTag);
} else {
int duplicateRuntimeId = addedStatesMap.get(blockTag);
int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1);
if (duplicateRuntimeId == -1) {
GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!");
} else {
@ -274,27 +265,6 @@ public class BlockTranslator {
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) {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId);
}

View file

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

View file

@ -25,42 +25,43 @@
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.nukkitx.math.vector.Vector3i;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.CompoundTagBuilder;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.utils.BlockEntityUtils;
import com.nukkitx.nbt.tag.ByteTag;
import com.nukkitx.nbt.tag.Tag;
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) {
byte bedcolor = BlockTranslator.getBedColor(blockState);
// If Bed Color is not -1 then it is indeed a bed with a color.
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
);
}
@Override
public boolean isBlock(BlockState blockState) {
return BlockStateValues.getBedColor(blockState) != -1;
}
public static com.nukkitx.nbt.tag.CompoundTag getBedTag(byte bedcolor, Position pos) {
CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder()
.intTag("x", pos.getX())
.intTag("y", pos.getY())
.intTag("z", pos.getZ())
.stringTag("id", "Bed");
tagBuilder.byteTag("color", bedcolor);
@Override
public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>();
byte bedcolor = BlockStateValues.getBedColor(blockState);
// Just in case...
if (bedcolor == -1) bedcolor = 0;
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();
}
}

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;
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.IntTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.Tag;
import lombok.AllArgsConstructor;
import org.geysermc.connector.utils.BlockEntityUtils;
import java.util.List;
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 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 y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue()));
int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue()));
CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder();
translateTag(tag).forEach(tagBuilder::tag);
translateTag(tag, blockState).forEach(tagBuilder::tag);
return tagBuilder.buildRootTag();
}

View file

@ -25,6 +25,7 @@
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.ListTag;
import com.nukkitx.nbt.CompoundTagBuilder;
@ -37,10 +38,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@BlockEntity(name = "Campfire", delay = false, regex = "campfire")
public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
@Override
public List<Tag<?>> translateTag(CompoundTag tag) {
public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>();
ListTag items = tag.get("Items");
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;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.tag.Tag;
import java.util.ArrayList;
import java.util.List;
@BlockEntity(name = "Empty", delay = false, regex = "")
public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
@Override
public List<Tag<?>> translateTag(CompoundTag tag) {
public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
return new ArrayList<>();
}

View file

@ -25,6 +25,7 @@
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.LongTag;
import com.nukkitx.nbt.CompoundTagBuilder;
@ -35,9 +36,11 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@BlockEntity(name = "EndGateway", delay = true, regex = "end_gateway")
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
@Override
public List<Tag<?>> translateTag(CompoundTag tag) {
public List<Tag<?>> translateTag(CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>();
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

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

View file

@ -25,43 +25,47 @@
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.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.ByteTag;
import com.nukkitx.nbt.tag.CompoundTag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.BlockTranslator;
import org.geysermc.connector.utils.BlockEntityUtils;
import com.nukkitx.nbt.tag.FloatTag;
import com.nukkitx.nbt.tag.Tag;
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) {
byte skullVariant = BlockTranslator.getSkullVariant(blockState);
byte rotation = BlockTranslator.getSkullRotation(blockState);
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
);
}
@Override
public boolean isBlock(BlockState blockState) {
return BlockStateValues.getSkullVariant(blockState) != -1;
}
public static CompoundTag getSkullTag(byte skullvariant, Position pos, byte rotation) {
CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder()
.intTag("x", pos.getX())
.intTag("y", pos.getY())
.intTag("z", pos.getZ())
.stringTag("id", "Skull")
.floatTag("Rotation", rotation * 22.5f);
tagBuilder.byteTag("SkullType", skullvariant);
@Override
public List<Tag<?>> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) {
List<Tag<?>> tags = new ArrayList<>();
byte skullVariant = BlockStateValues.getSkullVariant(blockState);
float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f;
// Just in case...
if (skullVariant == -1) skullVariant = 0;
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();
}
}

View file

@ -85,7 +85,7 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer());
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream);
for (CompoundTag blockEntity : chunkData.blockEntities) {
for (CompoundTag blockEntity : chunkData.getBlockEntities()) {
nbtStream.write(blockEntity);
}
@ -102,31 +102,15 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
levelChunkPacket.setData(payload);
session.getUpstream().sendPacket(levelChunkPacket);
// Signs have to be sent after the chunk since in later versions they aren't included in the block entities
for (Object2IntMap.Entry<CompoundTag> blockEntityEntry : chunkData.signs.object2IntEntrySet()) {
// 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.getLoadBlockEntitiesLater().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));
}
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) {
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.translators.PacketTranslator;
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.utils.BlockEntityUtils;
import org.geysermc.connector.utils.ChunkUtils;
import java.util.concurrent.TimeUnit;
@Translator(packet = ServerUpdateTileEntityPacket.class)
public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdateTileEntityPacket> {
// This should be modified if sign text is not showing up
private static final int DELAY = 500;
@Override
public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) {
String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name());
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
session.getConnector().getGeneralThreadPool().schedule(() ->
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag("Sign", packet.getNbt()), packet.getPosition()),
5,
TimeUnit.SECONDS
);
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()),
DELAY, TimeUnit.MILLISECONDS);
} 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.Object2IntOpenHashMap;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator;
import org.geysermc.connector.network.translators.block.entity.SkullBlockEntityTranslator;
import org.geysermc.connector.world.chunk.ChunkPosition;
import org.geysermc.connector.network.translators.block.entity.*;
import org.geysermc.connector.network.translators.Translators;
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 java.util.HashMap;
@ -56,12 +55,20 @@ import static org.geysermc.connector.network.translators.block.BlockTranslator.B
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) {
ChunkData chunkData = new ChunkData();
Chunk[] chunks = column.getChunks();
chunkData.sections = new ChunkSection[chunks.length];
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++) {
chunkData.sections[chunkY] = new ChunkSection();
Chunk chunk = chunks[chunkY];
@ -76,21 +83,19 @@ public class ChunkUtils {
BlockState blockState = chunk.get(x, y, z);
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);
chunkData.signs.put(Translators.getBlockEntityTranslators().get("Sign").getDefaultBedrockTag("Sign", pos.getX(), pos.getY(), pos.getZ()), blockState.getId());
} else if (BlockTranslator.getBlockEntityString(blockState) != null && BlockTranslator.getBlockEntityString(blockState).contains("end_gateway")) {
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
chunkData.gateways.put(Translators.getBlockEntityTranslators().get("EndGateway").getDefaultBedrockTag("EndGateway", pos.getX(), pos.getY(), pos.getZ()), blockState.getId());
} else if (BlockTranslator.getBedColor(blockState) > -1) {
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
// Beds need to be updated separately to add the bed color tag
// 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);
blockEntityPositions.put(pos, blockState);
// If there is a delay required for the block, allow it.
if (blockEntityTranslator.getClass().getAnnotation(BlockEntity.class).delay()) {
chunkData.loadBlockEntitiesLater.put(blockEntityTranslator.getDefaultBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockTranslator.getBlockEntityString(blockState)),
pos.getX(), pos.getY(), pos.getZ()), blockState.getId());
} else {
section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id);
}
} else {
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];
@ -116,7 +122,8 @@ public class ChunkUtils {
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
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;
@ -162,10 +169,18 @@ public class ChunkUtils {
}
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
BedBlockEntityTranslator.checkForBedColor(session, blockState, position);
SkullBlockEntityTranslator.checkForSkullVariant(session, blockState, position);
// Iterates through all block entity translators and determines if the block state needs to be saved
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) {
@ -196,10 +211,9 @@ public class ChunkUtils {
public static final class ChunkData {
public ChunkSection[] sections;
public com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0];
public Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> signs = new Object2IntOpenHashMap<>();
public Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> gateways = new Object2IntOpenHashMap<>();
public Map<Position, BlockState> beds = new HashMap<>();
public Map<Position, BlockState> skulls = new HashMap<>();
@Getter
private com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0];
@Getter
private Object2IntMap<com.nukkitx.nbt.tag.CompoundTag> loadBlockEntitiesLater = new Object2IntOpenHashMap<>();
}
}