Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 06:50:09 +01:00
Initial update to 1.18 protocol
Dieser Commit ist enthalten in:
Ursprung
393c2b0f91
Commit
6b012778d8
@ -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.Column;
|
||||
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(Column chunk) {
|
||||
public void addToCache(int x, int z, DataPalette[] chunks) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
|
||||
GeyserColumn geyserColumn = GeyserColumn.from(this, chunk);
|
||||
chunks.put(chunkPosition, geyserColumn);
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(x, z);
|
||||
GeyserColumn geyserColumn = GeyserColumn.from(chunks);
|
||||
this.chunks.put(chunkPosition, geyserColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,11 +89,11 @@ public class ChunkCache {
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk chunk = column.getChunks()[(y - minY) >> 4];
|
||||
DataPalette chunk = column.getChunks()[(y - minY) >> 4];
|
||||
if (chunk == null) {
|
||||
if (block != BlockStateValues.JAVA_AIR_ID) {
|
||||
// A previously empty chunk, which is no longer empty as a block has been added to it
|
||||
chunk = new Chunk();
|
||||
chunk = DataPalette.createForChunk();
|
||||
// Fixes the chunk assuming that all blocks is the `block` variable we are updating. /shrug
|
||||
chunk.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID);
|
||||
column.getChunks()[(y - minY) >> 4] = chunk;
|
||||
@ -122,7 +121,7 @@ public class ChunkCache {
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
Chunk chunk = column.getChunks()[(y - minY) >> 4];
|
||||
DataPalette chunk = column.getChunks()[(y - minY) >> 4];
|
||||
if (chunk != null) {
|
||||
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
}
|
||||
|
@ -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()));
|
||||
|
@ -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.UpdatedTileType;
|
||||
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);
|
||||
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2021 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.java.level;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelChunkPacket;
|
||||
import com.nukkitx.nbt.NBTOutputStream;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.network.VarInts;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
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.chunk.ChunkSection;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT;
|
||||
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT_OVERWORLD;
|
||||
|
||||
@Translator(packet = ClientboundLevelChunkPacket.class)
|
||||
public class JavaLevelChunkTranslator extends PacketTranslator<ClientboundLevelChunkPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundLevelChunkPacket packet) {
|
||||
if (session.isSpawned()) {
|
||||
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
|
||||
}
|
||||
|
||||
session.getChunkCache().addToCache(packet.getColumn());
|
||||
Column column = packet.getColumn();
|
||||
|
||||
// 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, column, yOffset);
|
||||
ChunkSection[] sections = chunkData.sections();
|
||||
|
||||
// Find highest section
|
||||
int 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++) {
|
||||
ChunkSection 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
|
||||
|
||||
// Allocate output buffer
|
||||
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||
byte[] payload;
|
||||
try {
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
ChunkSection 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;
|
||||
if (biomeYOffset < yOffset) {
|
||||
// Ignore this biome section since it goes below the height of the Java world
|
||||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||
continue;
|
||||
}
|
||||
BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
|
||||
int remainingEmptyBiomes = 32 - sectionCount;
|
||||
for (int i = 0; i < remainingEmptyBiomes; i++) {
|
||||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||
}
|
||||
|
||||
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
|
||||
|
||||
// Encode tile entities into buffer
|
||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||
for (NbtMap blockEntity : chunkData.blockEntities()) {
|
||||
nbtStream.writeTag(blockEntity);
|
||||
}
|
||||
|
||||
// Copy data into byte[], because the protocol lib really likes things that are s l o w
|
||||
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
|
||||
} catch (IOException e) {
|
||||
session.getConnector().getLogger().error("IO error while encoding chunk", e);
|
||||
return;
|
||||
} finally {
|
||||
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
||||
}
|
||||
|
||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||
levelChunkPacket.setSubChunksLength(sectionCount);
|
||||
levelChunkPacket.setCachingEnabled(false);
|
||||
levelChunkPacket.setChunkX(column.getX());
|
||||
levelChunkPacket.setChunkZ(column.getZ());
|
||||
levelChunkPacket.setData(payload);
|
||||
session.sendUpstreamPacket(levelChunkPacket);
|
||||
}
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2021 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.java.level;
|
||||
|
||||
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.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;
|
||||
import com.nukkitx.network.VarInts;
|
||||
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.ChunkSection;
|
||||
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.*;
|
||||
|
||||
@Translator(packet = ClientboundLevelChunkWithLightPacket.class)
|
||||
public class JavaLevelChunkWithLightTranslator extends PacketTranslator<ClientboundLevelChunkWithLightPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacket packet) {
|
||||
if (session.isSpawned()) {
|
||||
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
|
||||
}
|
||||
|
||||
//todo session.getChunkCache().addToCache(packet.getColumn());
|
||||
|
||||
// Ensure that, if the player is using lower world heights, the position is not offset
|
||||
int yOffset = session.getChunkCache().getChunkMinY();
|
||||
|
||||
// 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()];
|
||||
|
||||
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;
|
||||
|
||||
int sectionCount;
|
||||
byte[] payload;
|
||||
ByteBuf byteBuf = null;
|
||||
ChunkSection[] sections = new ChunkSection[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;
|
||||
}
|
||||
|
||||
Chunk javaSection = Chunk.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
|
||||
ChunkSection section = new ChunkSection(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 ChunkSection(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++) {
|
||||
ChunkSection 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++) {
|
||||
ChunkSection section = sections[i];
|
||||
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
// At this point we're dealing with Bedrock chunk sections
|
||||
int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
int biomeYOffset = dimensionOffset + i;
|
||||
if (biomeYOffset < yOffset) {
|
||||
// Ignore this biome section since it goes below the height of the Java world
|
||||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||
continue;
|
||||
}
|
||||
BiomeTranslator.toNewBedrockBiome(session, javaBiomes[i]).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
|
||||
int remainingEmptyBiomes = 32 - sectionCount;
|
||||
for (int i = 0; i < remainingEmptyBiomes; i++) {
|
||||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||
}
|
||||
|
||||
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
|
||||
|
||||
// Encode tile entities into buffer
|
||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||
for (NbtMap blockEntity : bedrockBlockEntities) {
|
||||
nbtStream.writeTag(blockEntity);
|
||||
}
|
||||
|
||||
// Copy data into byte[], because the protocol lib really likes things that are s l o w
|
||||
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
|
||||
} catch (IOException e) {
|
||||
session.getConnector().getLogger().error("IO error while encoding chunk", e);
|
||||
return;
|
||||
} finally {
|
||||
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(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(ChunkSection.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(ChunkSection.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,21 @@
|
||||
|
||||
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.Column;
|
||||
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 Column} that doesn't store
|
||||
* biomes or heightmaps.
|
||||
* Acts as a lightweight chunk class that doesn't store biomes, heightmaps or block entities.
|
||||
*/
|
||||
public class GeyserColumn {
|
||||
@Getter
|
||||
private final Chunk[] chunks;
|
||||
private final DataPalette[] chunks;
|
||||
|
||||
private GeyserColumn(Chunk[] chunks) {
|
||||
private GeyserColumn(DataPalette[] chunks) {
|
||||
this.chunks = chunks;
|
||||
}
|
||||
|
||||
public static GeyserColumn from(ChunkCache chunkCache, Column column) {
|
||||
int chunkHeightY = chunkCache.getChunkHeightY();
|
||||
Chunk[] chunks;
|
||||
if (chunkHeightY < column.getChunks().length) {
|
||||
chunks = new Chunk[chunkHeightY];
|
||||
// TODO addresses https://github.com/Steveice10/MCProtocolLib/pull/598#issuecomment-862782392
|
||||
System.arraycopy(column.getChunks(), 0, chunks, 0, chunks.length);
|
||||
} else {
|
||||
chunks = column.getChunks();
|
||||
}
|
||||
public static GeyserColumn from(DataPalette[] chunks) {
|
||||
return new GeyserColumn(chunks);
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,10 @@
|
||||
|
||||
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.Column;
|
||||
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;
|
||||
@ -40,27 +37,16 @@ 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.ChunkSection;
|
||||
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 +59,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 +92,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, Column column, int yOffset) {
|
||||
Chunk[] javaSections = column.getChunks();
|
||||
ChunkSection[] sections = new ChunkSection[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;
|
||||
}
|
||||
|
||||
Chunk 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
|
||||
ChunkSection section = new ChunkSection(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((column.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (column.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((column.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (column.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 ChunkSection(layers);
|
||||
}
|
||||
|
||||
CompoundTag[] blockEntities = column.getTileEntities();
|
||||
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;
|
||||
Chunk section = column.getChunks()[(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);
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren