mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge across 1.18 protocol changes
This commit is contained in:
commit
4bbea1de68
13 changed files with 316 additions and 323 deletions
|
@ -35,20 +35,16 @@ import org.geysermc.connector.command.GeyserCommand;
|
|||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
|
||||
|
||||
private final CommandExecutor commandExecutor;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public GeyserBungeeCommandExecutor(GeyserConnector connector) {
|
||||
super("geyser");
|
||||
|
||||
this.commandExecutor = new CommandExecutor(connector);
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
<dependency>
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<artifactId>mcprotocollib</artifactId>
|
||||
<version>1.17.1-3-SNAPSHOT</version>
|
||||
<version>1.18-pre-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
|
||||
package org.geysermc.connector.network.session.cache;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
@ -57,14 +56,14 @@ public class ChunkCache {
|
|||
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
|
||||
}
|
||||
|
||||
public void addToCache(Chunk chunk) {
|
||||
public void addToCache(int x, int z, DataPalette[] chunks) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
|
||||
GeyserChunk geyserChunk = GeyserChunk.from(this, chunk);
|
||||
chunks.put(chunkPosition, geyserChunk);
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(x, z);
|
||||
GeyserChunk geyserChunk = GeyserChunk.from(chunks);
|
||||
this.chunks.put(chunkPosition, geyserChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,26 +84,26 @@ public class ChunkCache {
|
|||
return;
|
||||
}
|
||||
|
||||
if (y < minY || ((y - minY) >> 4) > chunk.getSections().length - 1) {
|
||||
if (y < minY || ((y - minY) >> 4) > chunk.sections().length - 1) {
|
||||
// Y likely goes above or below the height limit of this world
|
||||
return;
|
||||
}
|
||||
|
||||
ChunkSection section = chunk.getSections()[(y - minY) >> 4];
|
||||
if (section == null) {
|
||||
DataPalette palette = chunk.sections()[(y - minY) >> 4];
|
||||
if (palette == null) {
|
||||
if (block != BlockStateValues.JAVA_AIR_ID) {
|
||||
// A previously empty chunk, which is no longer empty as a block has been added to it
|
||||
section = new ChunkSection();
|
||||
palette = DataPalette.createForChunk();
|
||||
// Fixes the chunk assuming that all blocks is the `block` variable we are updating. /shrug
|
||||
section.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID);
|
||||
chunk.getSections()[(y - minY) >> 4] = section;
|
||||
palette.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID);
|
||||
chunk.sections()[(y - minY) >> 4] = palette;
|
||||
} else {
|
||||
// Nothing to update
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
section.set(x & 0xF, y & 0xF, z & 0xF, block);
|
||||
palette.set(x & 0xF, y & 0xF, z & 0xF, block);
|
||||
}
|
||||
|
||||
public int getBlockAt(int x, int y, int z) {
|
||||
|
@ -117,12 +116,12 @@ public class ChunkCache {
|
|||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
if (y < minY || ((y - minY) >> 4) > column.getSections().length - 1) {
|
||||
if (y < minY || ((y - minY) >> 4) > column.sections().length - 1) {
|
||||
// Y likely goes above or below the height limit of this world
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
ChunkSection chunk = column.getSections()[(y - minY) >> 4];
|
||||
DataPalette chunk = column.sections()[(y - minY) >> 4];
|
||||
if (chunk != null) {
|
||||
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
}
|
||||
|
@ -142,7 +141,7 @@ public class ChunkCache {
|
|||
/**
|
||||
* Manually clears all entries in the chunk cache.
|
||||
* The server is responsible for clearing chunk entries if out of render distance (for example) or switching dimensions,
|
||||
* but it is the client that must clear chunks in the event of proxy switches.
|
||||
* but it is the client that must clear sections in the event of proxy switches.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!cache) {
|
||||
|
|
|
@ -106,7 +106,8 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||
|
||||
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
|
||||
String locale = session.getLocale();
|
||||
ServerboundClientInformationPacket infoPacket = new ServerboundClientInformationPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false);
|
||||
// TODO customize
|
||||
ServerboundClientInformationPacket infoPacket = new ServerboundClientInformationPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false, true);
|
||||
session.sendDownstreamPacket(infoPacket);
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
||||
|
|
|
@ -56,7 +56,7 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
|
|||
case SWING_ARM:
|
||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||
break;
|
||||
case EAT_FOOD:
|
||||
case SWING_OFFHAND:
|
||||
// Use the OptionalPack to trigger the animation
|
||||
AnimateEntityPacket offHandPacket = new AnimateEntityPacket();
|
||||
offHandPacket.setAnimation("animation.player.attack.rotations.offhand");
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.connector.network.translators.java.level;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
|
||||
|
@ -61,7 +62,9 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
|
|||
} else {
|
||||
blockState = BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), blockState), packet.getPosition());
|
||||
Position position = packet.getPosition();
|
||||
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, position.getX(), position.getY(), position.getZ(),
|
||||
packet.getNbt(), blockState), packet.getPosition());
|
||||
// Check for custom skulls.
|
||||
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt().contains("SkullOwner")) {
|
||||
SkullBlockEntityTranslator.spawnPlayer(session, packet.getNbt(), blockState);
|
||||
|
|
|
@ -25,8 +25,19 @@
|
|||
|
||||
package org.geysermc.connector.network.translators.java.level;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelChunkWithLightPacket;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.github.steveice10.packetlib.io.NetInput;
|
||||
import com.github.steveice10.packetlib.io.stream.StreamNetInput;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NBTOutputStream;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
|
@ -35,17 +46,32 @@ import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.network.translators.world.BiomeTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.chunk.BlockStorage;
|
||||
import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion;
|
||||
import org.geysermc.connector.registry.BlockRegistries;
|
||||
import org.geysermc.connector.utils.BlockEntityUtils;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT;
|
||||
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT_OVERWORLD;
|
||||
import static org.geysermc.connector.utils.ChunkUtils.*;
|
||||
|
||||
@Translator(packet = ClientboundLevelChunkWithLightPacket.class)
|
||||
public class JavaLevelChunkWithLightTranslator extends PacketTranslator<ClientboundLevelChunkWithLightPacket> {
|
||||
|
@ -56,44 +82,235 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
|
||||
}
|
||||
|
||||
session.getChunkCache().addToCache(packet.getChunk());
|
||||
Chunk chunk = packet.getChunk();
|
||||
|
||||
// Ensure that, if the player is using lower world heights, the position is not offset
|
||||
int yOffset = session.getChunkCache().getChunkMinY();
|
||||
|
||||
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, chunk, yOffset);
|
||||
GeyserChunkSection[] sections = chunkData.sections();
|
||||
// Temporarily stores compound tags of Bedrock-only block entities
|
||||
List<NbtMap> bedrockOnlyBlockEntities = new ArrayList<>();
|
||||
DataPalette[] javaChunks = new DataPalette[session.getChunkCache().getChunkHeightY()];
|
||||
DataPalette[] javaBiomes = new DataPalette[session.getChunkCache().getChunkHeightY()];
|
||||
|
||||
// Find highest section
|
||||
int sectionCount = sections.length - 1;
|
||||
while (sectionCount >= 0 && sections[sectionCount] == null) {
|
||||
sectionCount--;
|
||||
}
|
||||
sectionCount++;
|
||||
BitSet waterloggedPaletteIds = new BitSet();
|
||||
BitSet pistonOrFlowerPaletteIds = new BitSet();
|
||||
|
||||
// Estimate chunk size
|
||||
int size = 0;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
GeyserChunkSection section = sections[i];
|
||||
size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize();
|
||||
}
|
||||
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
||||
size += 1; // Border blocks
|
||||
size += 1; // Extra data length (always 0)
|
||||
size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
|
||||
boolean overworld = session.getChunkCache().isExtendedHeight();
|
||||
int maxBedrockSectionY = ((overworld ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT) >> 4) - 1;
|
||||
|
||||
// Allocate output buffer
|
||||
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||
int sectionCount;
|
||||
byte[] payload;
|
||||
ByteBuf byteBuf = null;
|
||||
GeyserChunkSection[] sections = new GeyserChunkSection[javaChunks.length - yOffset];
|
||||
|
||||
try {
|
||||
NetInput in = new StreamNetInput(new ByteArrayInputStream(packet.getChunkData()));
|
||||
for (int sectionY = 0; sectionY < session.getChunkCache().getChunkHeightY(); sectionY++) {
|
||||
int bedrockSectionY = sectionY + (yOffset - ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4));
|
||||
if (bedrockSectionY < 0 || maxBedrockSectionY < bedrockSectionY) {
|
||||
// Ignore this chunk section since it goes outside the bounds accepted by the Bedrock client
|
||||
continue;
|
||||
}
|
||||
|
||||
ChunkSection javaSection = ChunkSection.read(in);
|
||||
javaChunks[sectionY] = javaSection.getChunkData();
|
||||
javaBiomes[sectionY] = javaSection.getBiomeData();
|
||||
|
||||
// No need to encode an empty section...
|
||||
if (javaSection.isBlockCountEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Palette javaPalette = javaSection.getChunkData().getPalette();
|
||||
BitStorage javaData = javaSection.getChunkData().getStorage();
|
||||
|
||||
if (javaPalette instanceof GlobalPalette) {
|
||||
// As this is the global palette, simply iterate through the whole chunk section once
|
||||
GeyserChunkSection section = new GeyserChunkSection(session.getBlockMappings().getBedrockAirId());
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int javaId = javaData.get(yzx);
|
||||
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId);
|
||||
int xzy = indexYZXtoXZY(yzx);
|
||||
section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId);
|
||||
|
||||
if (BlockRegistries.WATERLOGGED.get().contains(javaId)) {
|
||||
section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockMappings().getBedrockWaterId());
|
||||
}
|
||||
|
||||
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) {
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
|
||||
Vector3i.from((packet.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)),
|
||||
javaId
|
||||
));
|
||||
}
|
||||
}
|
||||
sections[bedrockSectionY] = section;
|
||||
continue;
|
||||
}
|
||||
|
||||
IntList bedrockPalette = new IntArrayList(javaPalette.size());
|
||||
waterloggedPaletteIds.clear();
|
||||
pistonOrFlowerPaletteIds.clear();
|
||||
|
||||
// Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go
|
||||
for (int i = 0; i < javaPalette.size(); i++) {
|
||||
int javaId = javaPalette.idToState(i);
|
||||
bedrockPalette.add(session.getBlockMappings().getBedrockBlockId(javaId));
|
||||
|
||||
if (BlockRegistries.WATERLOGGED.get().contains(javaId)) {
|
||||
waterloggedPaletteIds.set(i);
|
||||
}
|
||||
|
||||
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) {
|
||||
pistonOrFlowerPaletteIds.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Bedrock-exclusive block entities
|
||||
// We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data
|
||||
// for no reason, as most sections will not contain any pistons or flower pots
|
||||
if (!pistonOrFlowerPaletteIds.isEmpty()) {
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
if (pistonOrFlowerPaletteIds.get(paletteId)) {
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
|
||||
Vector3i.from((packet.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)),
|
||||
javaPalette.idToState(paletteId)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE);
|
||||
BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette);
|
||||
BlockStorage[] layers;
|
||||
|
||||
// Convert data array from YZX to XZY coordinate order
|
||||
if (waterloggedPaletteIds.isEmpty()) {
|
||||
// No blocks are waterlogged, simply convert coordinate order
|
||||
// This could probably be optimized further...
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx));
|
||||
}
|
||||
|
||||
layers = new BlockStorage[]{ layer0 };
|
||||
} else {
|
||||
// The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for
|
||||
// layer 1 with palette ID 1 indicating water
|
||||
int[] layer1Data = new int[BlockStorage.SIZE >> 5];
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
int xzy = indexYZXtoXZY(yzx);
|
||||
bedrockData.set(xzy, paletteId);
|
||||
|
||||
if (waterloggedPaletteIds.get(paletteId)) {
|
||||
layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F);
|
||||
}
|
||||
}
|
||||
|
||||
// V1 palette
|
||||
IntList layer1Palette = new IntArrayList(2);
|
||||
layer1Palette.add(session.getBlockMappings().getBedrockAirId()); // Air - see BlockStorage's constructor for more information
|
||||
layer1Palette.add(session.getBlockMappings().getBedrockWaterId());
|
||||
|
||||
layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) };
|
||||
}
|
||||
|
||||
sections[bedrockSectionY] = new GeyserChunkSection(layers);
|
||||
}
|
||||
|
||||
session.getChunkCache().addToCache(packet.getX(), packet.getZ(), javaChunks);
|
||||
|
||||
BlockEntityInfo[] blockEntities = packet.getBlockEntities();
|
||||
NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()];
|
||||
int blockEntityCount = 0;
|
||||
while (blockEntityCount < blockEntities.length) {
|
||||
BlockEntityInfo blockEntity = blockEntities[blockEntityCount];
|
||||
CompoundTag tag = blockEntity.getNbt();
|
||||
// TODO use the actual name
|
||||
String tagName;
|
||||
if (tag != null) {
|
||||
Tag idTag = tag.get("id");
|
||||
if (idTag != null) {
|
||||
tagName = (String) idTag.getValue();
|
||||
} else {
|
||||
tagName = "Empty";
|
||||
// Sometimes legacy tags have their ID be a StringTag with empty value
|
||||
for (Tag subTag : tag) {
|
||||
if (subTag instanceof StringTag stringTag) {
|
||||
if (stringTag.getValue().isEmpty()) {
|
||||
tagName = stringTag.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tagName.equals("Empty")) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Got tag with no id: " + tag.getValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tagName = "Empty";
|
||||
}
|
||||
|
||||
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
|
||||
int x = blockEntity.getX();
|
||||
int y = blockEntity.getY();
|
||||
int z = blockEntity.getZ();
|
||||
|
||||
// Get the Java block state ID from block entity position
|
||||
DataPalette section = javaChunks[(blockEntity.getY() >> 4) - yOffset];
|
||||
int blockState = section.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
|
||||
if (tagName.equals("minecraft:lectern") && BlockStateValues.getLecternBookStates().get(blockState)) {
|
||||
// If getLecternBookStates is false, let's just treat it like a normal block entity
|
||||
bedrockBlockEntities[blockEntityCount] = session.getConnector().getWorldManager().getLecternDataAt(
|
||||
session, blockEntity.getX(), blockEntity.getY(), blockEntity.getZ(), true);
|
||||
blockEntityCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
|
||||
bedrockBlockEntities[blockEntityCount] = blockEntityTranslator.getBlockEntityTag(tagName, x, y, z, tag, blockState);
|
||||
|
||||
// Check for custom skulls
|
||||
if (session.getPreferencesCache().showCustomSkulls() && tag != null && tag.contains("SkullOwner")) {
|
||||
SkullBlockEntityTranslator.spawnPlayer(session, tag, blockState);
|
||||
}
|
||||
blockEntityCount++;
|
||||
}
|
||||
|
||||
// Append Bedrock-exclusive block entities to output array
|
||||
for (NbtMap tag : bedrockOnlyBlockEntities) {
|
||||
bedrockBlockEntities[blockEntityCount] = tag;
|
||||
blockEntityCount++;
|
||||
}
|
||||
|
||||
// Find highest section
|
||||
sectionCount = sections.length - 1;
|
||||
while (sectionCount >= 0 && sections[sectionCount] == null) {
|
||||
sectionCount--;
|
||||
}
|
||||
sectionCount++;
|
||||
|
||||
// Estimate chunk size
|
||||
int size = 0;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
GeyserChunkSection section = sections[i];
|
||||
size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize();
|
||||
}
|
||||
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
||||
size += 1; // Border blocks
|
||||
size += 1; // Extra data length (always 0)
|
||||
size += bedrockBlockEntities.length * 64; // Conservative estimate of 64 bytes per tile entity
|
||||
|
||||
// Allocate output buffer
|
||||
byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
GeyserChunkSection section = sections[i];
|
||||
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
// At this point we're dealing with Bedrock chunk sections
|
||||
boolean overworld = session.getChunkCache().isExtendedHeight();
|
||||
int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
int biomeYOffset = dimensionOffset + i;
|
||||
|
@ -102,7 +319,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||
continue;
|
||||
}
|
||||
BiomeTranslator.toNewBedrockBiome(session, chunk.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf);
|
||||
BiomeTranslator.toNewBedrockBiome(session, javaBiomes[i]).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
|
||||
|
@ -116,7 +333,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
|
||||
// Encode tile entities into buffer
|
||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||
for (NbtMap blockEntity : chunkData.blockEntities()) {
|
||||
for (NbtMap blockEntity : bedrockBlockEntities) {
|
||||
nbtStream.writeTag(blockEntity);
|
||||
}
|
||||
|
||||
|
@ -126,15 +343,17 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
session.getConnector().getLogger().error("IO error while encoding chunk", e);
|
||||
return;
|
||||
} finally {
|
||||
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
||||
if (byteBuf != null) {
|
||||
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
||||
}
|
||||
}
|
||||
|
||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||
levelChunkPacket.setSubChunksLength(sectionCount);
|
||||
levelChunkPacket.setCachingEnabled(false);
|
||||
levelChunkPacket.setChunkX(chunk.getX());
|
||||
levelChunkPacket.setChunkZ(chunk.getZ());
|
||||
levelChunkPacket.setChunkX(packet.getX());
|
||||
levelChunkPacket.setChunkZ(packet.getZ());
|
||||
levelChunkPacket.setData(payload);
|
||||
session.sendUpstreamPacket(levelChunkPacket);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.SingletonPalette;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
@ -98,33 +100,37 @@ public class BiomeTranslator {
|
|||
return bedrockData;
|
||||
}
|
||||
|
||||
public static BlockStorage toNewBedrockBiome(GeyserSession session, int[] biomeData, int ySection) {
|
||||
public static BlockStorage toNewBedrockBiome(GeyserSession session, DataPalette biomeData) {
|
||||
Int2IntMap biomeTranslations = session.getBiomeTranslations();
|
||||
// As of 1.17.10: the client expects the same format as a chunk but filled with biomes
|
||||
BlockStorage storage = new BlockStorage(0);
|
||||
// As of 1.18 this is the same as Java Edition
|
||||
|
||||
int biomeY = ySection << 2;
|
||||
int javaOffsetY = biomeY << 4;
|
||||
// Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries
|
||||
for (int i = 0; i < 64; i++) {
|
||||
int javaId = biomeData[javaOffsetY | i];
|
||||
int x = i & 3;
|
||||
int y = (i >> 4) & 3;
|
||||
int z = (i >> 2) & 3;
|
||||
// Get the Bedrock biome ID override
|
||||
int biomeId = biomeTranslations.get(javaId);
|
||||
int idx = storage.idFor(biomeId);
|
||||
// Convert biome coordinates into block coordinates
|
||||
// Bedrock expects a full 4096 blocks
|
||||
for (int blockX = x << 2; blockX < (x << 2) + 4; blockX++) {
|
||||
for (int blockZ = z << 2; blockZ < (z << 2) + 4; blockZ++) {
|
||||
for (int blockY = y << 2; blockY < (y << 2) + 4; blockY++) {
|
||||
storage.getBitArray().set(GeyserChunkSection.blockPosition(blockX, blockY, blockZ), idx);
|
||||
if (biomeData.getPalette() instanceof SingletonPalette palette) {
|
||||
int biomeId = biomeTranslations.get(palette.idToState(0));
|
||||
return new BlockStorage(biomeId);
|
||||
} else {
|
||||
BlockStorage storage = new BlockStorage(0);
|
||||
|
||||
// Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries
|
||||
for (int i = 0; i < 64; i++) {
|
||||
int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i));
|
||||
int x = i & 3;
|
||||
int y = (i >> 4) & 3;
|
||||
int z = (i >> 2) & 3;
|
||||
// Get the Bedrock biome ID override
|
||||
int biomeId = biomeTranslations.get(javaId);
|
||||
int idx = storage.idFor(biomeId);
|
||||
// Convert biome coordinates into block coordinates
|
||||
// Bedrock expects a full 4096 blocks
|
||||
for (int blockX = x << 2; blockX < (x << 2) + 4; blockX++) {
|
||||
for (int blockZ = z << 2; blockZ < (z << 2) + 4; blockZ++) {
|
||||
for (int blockY = y << 2; blockY < (y << 2) + 4; blockY++) {
|
||||
storage.getBitArray().set(GeyserChunkSection.blockPosition(blockX, blockY, blockZ), idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,7 @@ public abstract class BlockEntityTranslator {
|
|||
|
||||
public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState);
|
||||
|
||||
public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) {
|
||||
int x = ((IntTag) tag.getValue().get("x")).getValue();
|
||||
int y = ((IntTag) tag.getValue().get("y")).getValue();
|
||||
int z = ((IntTag) tag.getValue().get("z")).getValue();
|
||||
|
||||
public NbtMap getBlockEntityTag(String id, int x, int y, int z, CompoundTag tag, int blockState) {
|
||||
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z);
|
||||
translateTag(tagBuilder, tag, blockState);
|
||||
return tagBuilder.build();
|
||||
|
|
|
@ -25,33 +25,15 @@
|
|||
|
||||
package org.geysermc.connector.network.translators.world.chunk;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.network.session.cache.ChunkCache;
|
||||
|
||||
/**
|
||||
* Acts as a lightweight version of {@link Chunk} that doesn't store
|
||||
* biomes or heightmaps.
|
||||
* Acts as a lightweight chunk class that doesn't store biomes, heightmaps or block entities.
|
||||
*/
|
||||
public class GeyserChunk {
|
||||
@Getter
|
||||
private final ChunkSection[] sections;
|
||||
public record GeyserChunk(@Getter DataPalette[] sections) {
|
||||
|
||||
private GeyserChunk(ChunkSection[] sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
|
||||
public static GeyserChunk from(ChunkCache chunkCache, Chunk chunk) {
|
||||
int chunkHeightY = chunkCache.getChunkHeightY();
|
||||
ChunkSection[] sections;
|
||||
if (chunkHeightY < chunk.getSections().length) {
|
||||
sections = new ChunkSection[chunkHeightY];
|
||||
// TODO addresses https://github.com/Steveice10/MCProtocolLib/pull/598#issuecomment-862782392
|
||||
System.arraycopy(chunk.getSections(), 0, sections, 0, sections.length);
|
||||
} else {
|
||||
sections = chunk.getSections();
|
||||
}
|
||||
public static GeyserChunk from(DataPalette[] sections) {
|
||||
return new GeyserChunk(sections);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ import java.util.Map;
|
|||
public class BlockEntityUtils {
|
||||
/**
|
||||
* A list of all block entities that require the Java block state in order to fill out their block entity information.
|
||||
* This list will be smaller with cache chunks on as we don't need to double-cache data
|
||||
* This list will be smaller with cache sections on as we don't need to double-cache data
|
||||
*/
|
||||
public static final ObjectArrayList<BedrockOnlyBlockEntity> BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>();
|
||||
|
||||
|
|
|
@ -25,42 +25,26 @@
|
|||
|
||||
package org.geysermc.connector.utils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.nukkitx.math.vector.Vector2i;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.chunk.BlockStorage;
|
||||
import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion;
|
||||
import org.geysermc.connector.registry.BlockRegistries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.geysermc.connector.network.translators.world.block.BlockStateValues.JAVA_AIR_ID;
|
||||
|
||||
@UtilityClass
|
||||
|
@ -73,8 +57,8 @@ public class ChunkUtils {
|
|||
/**
|
||||
* The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
|
||||
*/
|
||||
private static final int MAXIMUM_ACCEPTED_HEIGHT = 256;
|
||||
private static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384;
|
||||
public static final int MAXIMUM_ACCEPTED_HEIGHT = 256;
|
||||
public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384;
|
||||
|
||||
public static final byte[] EMPTY_CHUNK_DATA;
|
||||
public static final byte[] EMPTY_BIOME_DATA;
|
||||
|
@ -106,200 +90,10 @@ public class ChunkUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static int indexYZXtoXZY(int yzx) {
|
||||
public static int indexYZXtoXZY(int yzx) {
|
||||
return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8);
|
||||
}
|
||||
|
||||
public static ChunkData translateToBedrock(GeyserSession session, Chunk chunk, int yOffset) {
|
||||
ChunkSection[] javaSections = chunk.getSections();
|
||||
GeyserChunkSection[] sections = new GeyserChunkSection[javaSections.length - yOffset];
|
||||
|
||||
// Temporarily stores compound tags of Bedrock-only block entities
|
||||
List<NbtMap> bedrockOnlyBlockEntities = new ArrayList<>();
|
||||
|
||||
BitSet waterloggedPaletteIds = new BitSet();
|
||||
BitSet pistonOrFlowerPaletteIds = new BitSet();
|
||||
|
||||
boolean overworld = session.getChunkCache().isExtendedHeight();
|
||||
int maxBedrockSectionY = ((overworld ? MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD : MAXIMUM_ACCEPTED_HEIGHT) >> 4) - 1;
|
||||
|
||||
for (int sectionY = 0; sectionY < javaSections.length; sectionY++) {
|
||||
int bedrockSectionY = sectionY + (yOffset - ((overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4));
|
||||
if (bedrockSectionY < 0 || maxBedrockSectionY < bedrockSectionY) {
|
||||
// Ignore this chunk section since it goes outside the bounds accepted by the Bedrock client
|
||||
continue;
|
||||
}
|
||||
|
||||
ChunkSection javaSection = javaSections[sectionY];
|
||||
|
||||
// No need to encode an empty section...
|
||||
if (javaSection == null || javaSection.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Palette javaPalette = javaSection.getPalette();
|
||||
BitStorage javaData = javaSection.getStorage();
|
||||
|
||||
if (javaPalette instanceof GlobalPalette) {
|
||||
// As this is the global palette, simply iterate through the whole chunk section once
|
||||
GeyserChunkSection section = new GeyserChunkSection(session.getBlockMappings().getBedrockAirId());
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int javaId = javaData.get(yzx);
|
||||
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId);
|
||||
int xzy = indexYZXtoXZY(yzx);
|
||||
section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId);
|
||||
|
||||
if (BlockRegistries.WATERLOGGED.get().contains(javaId)) {
|
||||
section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockMappings().getBedrockWaterId());
|
||||
}
|
||||
|
||||
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) {
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
|
||||
Vector3i.from((chunk.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (chunk.getZ() << 4) + ((yzx >> 4) & 0xF)),
|
||||
javaId
|
||||
));
|
||||
}
|
||||
}
|
||||
sections[bedrockSectionY] = section;
|
||||
continue;
|
||||
}
|
||||
|
||||
IntList bedrockPalette = new IntArrayList(javaPalette.size());
|
||||
waterloggedPaletteIds.clear();
|
||||
pistonOrFlowerPaletteIds.clear();
|
||||
|
||||
// Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go
|
||||
for (int i = 0; i < javaPalette.size(); i++) {
|
||||
int javaId = javaPalette.idToState(i);
|
||||
bedrockPalette.add(session.getBlockMappings().getBedrockBlockId(javaId));
|
||||
|
||||
if (BlockRegistries.WATERLOGGED.get().contains(javaId)) {
|
||||
waterloggedPaletteIds.set(i);
|
||||
}
|
||||
|
||||
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) {
|
||||
pistonOrFlowerPaletteIds.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Bedrock-exclusive block entities
|
||||
// We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data
|
||||
// for no reason, as most sections will not contain any pistons or flower pots
|
||||
if (!pistonOrFlowerPaletteIds.isEmpty()) {
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
if (pistonOrFlowerPaletteIds.get(paletteId)) {
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
|
||||
Vector3i.from((chunk.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (chunk.getZ() << 4) + ((yzx >> 4) & 0xF)),
|
||||
javaPalette.idToState(paletteId)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE);
|
||||
BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette);
|
||||
BlockStorage[] layers;
|
||||
|
||||
// Convert data array from YZX to XZY coordinate order
|
||||
if (waterloggedPaletteIds.isEmpty()) {
|
||||
// No blocks are waterlogged, simply convert coordinate order
|
||||
// This could probably be optimized further...
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx));
|
||||
}
|
||||
|
||||
layers = new BlockStorage[]{ layer0 };
|
||||
} else {
|
||||
// The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for
|
||||
// layer 1 with palette ID 1 indicating water
|
||||
int[] layer1Data = new int[BlockStorage.SIZE >> 5];
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
int xzy = indexYZXtoXZY(yzx);
|
||||
bedrockData.set(xzy, paletteId);
|
||||
|
||||
if (waterloggedPaletteIds.get(paletteId)) {
|
||||
layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F);
|
||||
}
|
||||
}
|
||||
|
||||
// V1 palette
|
||||
IntList layer1Palette = new IntArrayList(2);
|
||||
layer1Palette.add(session.getBlockMappings().getBedrockAirId()); // Air - see BlockStorage's constructor for more information
|
||||
layer1Palette.add(session.getBlockMappings().getBedrockWaterId());
|
||||
|
||||
layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) };
|
||||
}
|
||||
|
||||
sections[bedrockSectionY] = new GeyserChunkSection(layers);
|
||||
}
|
||||
|
||||
CompoundTag[] blockEntities = chunk.getBlockEntities();
|
||||
NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()];
|
||||
int i = 0;
|
||||
while (i < blockEntities.length) {
|
||||
CompoundTag tag = blockEntities[i];
|
||||
String tagName;
|
||||
Tag idTag = tag.get("id");
|
||||
if (idTag != null) {
|
||||
tagName = (String) idTag.getValue();
|
||||
} else {
|
||||
tagName = "Empty";
|
||||
// Sometimes legacy tags have their ID be a StringTag with empty value
|
||||
for (Tag subTag : tag) {
|
||||
if (subTag instanceof StringTag stringTag) {
|
||||
if (stringTag.getValue().isEmpty()) {
|
||||
tagName = stringTag.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tagName.equals("Empty")) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Got tag with no id: " + tag.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
|
||||
int x = (int) tag.get("x").getValue();
|
||||
int y = (int) tag.get("y").getValue();
|
||||
int z = (int) tag.get("z").getValue();
|
||||
|
||||
// Get Java blockstate ID from block entity position
|
||||
int blockState = 0;
|
||||
ChunkSection section = chunk.getSections()[(y >> 4) - yOffset];
|
||||
if (section != null) {
|
||||
blockState = section.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
}
|
||||
|
||||
if (tagName.equals("minecraft:lectern") && BlockStateValues.getLecternBookStates().get(blockState)) {
|
||||
// If getLecternBookStates is false, let's just treat it like a normal block entity
|
||||
bedrockBlockEntities[i] = session.getConnector().getWorldManager().getLecternDataAt(session, x, y, z, true);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
|
||||
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState);
|
||||
|
||||
// Check for custom skulls
|
||||
if (session.getPreferencesCache().showCustomSkulls() && tag.contains("SkullOwner")) {
|
||||
SkullBlockEntityTranslator.spawnPlayer(session, tag, blockState);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Append Bedrock-exclusive block entities to output array
|
||||
for (NbtMap tag : bedrockOnlyBlockEntities) {
|
||||
bedrockBlockEntities[i] = tag;
|
||||
i++;
|
||||
}
|
||||
|
||||
return new ChunkData(sections, bedrockBlockEntities);
|
||||
}
|
||||
|
||||
public static void updateChunkPosition(GeyserSession session, Vector3i position) {
|
||||
Vector2i chunkPos = session.getLastChunkPosition();
|
||||
Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4);
|
||||
|
@ -452,7 +246,4 @@ public class ChunkUtils {
|
|||
double coordinateScale = ((DoubleTag) dimensionTag.get("coordinate_scale")).getValue();
|
||||
session.getWorldBorder().setWorldCoordinateScale(coordinateScale);
|
||||
}
|
||||
|
||||
public record ChunkData(GeyserChunkSection[] sections, NbtMap[] blockEntities) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,8 +99,8 @@ public class DimensionUtils {
|
|||
stopSoundPacket.setSoundName("");
|
||||
session.sendUpstreamPacket(stopSoundPacket);
|
||||
|
||||
// TODO - fix this hack of a fix by sending the final dimension switching logic after chunks have been sent.
|
||||
// The client wants chunks sent to it before it can successfully respawn.
|
||||
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
|
||||
// The client wants sections sent to it before it can successfully respawn.
|
||||
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue