Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 06:50:09 +01:00
Merge across 1.18 protocol changes
Dieser Commit ist enthalten in:
Commit
4bbea1de68
@ -35,20 +35,16 @@ import org.geysermc.connector.command.GeyserCommand;
|
|||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
|
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
|
||||||
|
|
||||||
private final CommandExecutor commandExecutor;
|
private final CommandExecutor commandExecutor;
|
||||||
private final GeyserConnector connector;
|
|
||||||
|
|
||||||
public GeyserBungeeCommandExecutor(GeyserConnector connector) {
|
public GeyserBungeeCommandExecutor(GeyserConnector connector) {
|
||||||
super("geyser");
|
super("geyser");
|
||||||
|
|
||||||
this.commandExecutor = new CommandExecutor(connector);
|
this.commandExecutor = new CommandExecutor(connector);
|
||||||
this.connector = connector;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,7 +149,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.steveice10</groupId>
|
<groupId>com.github.steveice10</groupId>
|
||||||
<artifactId>mcprotocollib</artifactId>
|
<artifactId>mcprotocollib</artifactId>
|
||||||
<version>1.17.1-3-SNAPSHOT</version>
|
<version>1.18-pre-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.session.cache;
|
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.DataPalette;
|
||||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -57,14 +56,14 @@ public class ChunkCache {
|
|||||||
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
|
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToCache(Chunk chunk) {
|
public void addToCache(int x, int z, DataPalette[] chunks) {
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
|
long chunkPosition = MathUtils.chunkPositionToLong(x, z);
|
||||||
GeyserChunk geyserChunk = GeyserChunk.from(this, chunk);
|
GeyserChunk geyserChunk = GeyserChunk.from(chunks);
|
||||||
chunks.put(chunkPosition, geyserChunk);
|
this.chunks.put(chunkPosition, geyserChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,26 +84,26 @@ public class ChunkCache {
|
|||||||
return;
|
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
|
// Y likely goes above or below the height limit of this world
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkSection section = chunk.getSections()[(y - minY) >> 4];
|
DataPalette palette = chunk.sections()[(y - minY) >> 4];
|
||||||
if (section == null) {
|
if (palette == null) {
|
||||||
if (block != BlockStateValues.JAVA_AIR_ID) {
|
if (block != BlockStateValues.JAVA_AIR_ID) {
|
||||||
// A previously empty chunk, which is no longer empty as a block has been added to it
|
// 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
|
// Fixes the chunk assuming that all blocks is the `block` variable we are updating. /shrug
|
||||||
section.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID);
|
palette.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID);
|
||||||
chunk.getSections()[(y - minY) >> 4] = section;
|
chunk.sections()[(y - minY) >> 4] = palette;
|
||||||
} else {
|
} else {
|
||||||
// Nothing to update
|
// Nothing to update
|
||||||
return;
|
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) {
|
public int getBlockAt(int x, int y, int z) {
|
||||||
@ -117,12 +116,12 @@ public class ChunkCache {
|
|||||||
return BlockStateValues.JAVA_AIR_ID;
|
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
|
// Y likely goes above or below the height limit of this world
|
||||||
return BlockStateValues.JAVA_AIR_ID;
|
return BlockStateValues.JAVA_AIR_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkSection chunk = column.getSections()[(y - minY) >> 4];
|
DataPalette chunk = column.sections()[(y - minY) >> 4];
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
||||||
}
|
}
|
||||||
@ -142,7 +141,7 @@ public class ChunkCache {
|
|||||||
/**
|
/**
|
||||||
* Manually clears all entries in the chunk cache.
|
* 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,
|
* 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() {
|
public void clear() {
|
||||||
if (!cache) {
|
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
|
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
|
||||||
String locale = session.getLocale();
|
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(infoPacket);
|
||||||
|
|
||||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
||||||
|
@ -56,7 +56,7 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
|
|||||||
case SWING_ARM:
|
case SWING_ARM:
|
||||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||||
break;
|
break;
|
||||||
case EAT_FOOD:
|
case SWING_OFFHAND:
|
||||||
// Use the OptionalPack to trigger the animation
|
// Use the OptionalPack to trigger the animation
|
||||||
AnimateEntityPacket offHandPacket = new AnimateEntityPacket();
|
AnimateEntityPacket offHandPacket = new AnimateEntityPacket();
|
||||||
offHandPacket.setAnimation("animation.player.attack.rotations.offhand");
|
offHandPacket.setAnimation("animation.player.attack.rotations.offhand");
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.translators.java.level;
|
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.entity.player.GameMode;
|
||||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket;
|
||||||
@ -61,7 +62,9 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
|
|||||||
} else {
|
} else {
|
||||||
blockState = BlockStateValues.JAVA_AIR_ID;
|
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.
|
// Check for custom skulls.
|
||||||
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt().contains("SkullOwner")) {
|
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt().contains("SkullOwner")) {
|
||||||
SkullBlockEntityTranslator.spawnPlayer(session, packet.getNbt(), blockState);
|
SkullBlockEntityTranslator.spawnPlayer(session, packet.getNbt(), blockState);
|
||||||
|
@ -25,8 +25,19 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.translators.java.level;
|
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.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.NBTOutputStream;
|
||||||
import com.nukkitx.nbt.NbtMap;
|
import com.nukkitx.nbt.NbtMap;
|
||||||
import com.nukkitx.nbt.NbtUtils;
|
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.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.buffer.ByteBufOutputStream;
|
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.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
import org.geysermc.connector.network.translators.world.BiomeTranslator;
|
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.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 org.geysermc.connector.utils.ChunkUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
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.*;
|
||||||
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT_OVERWORLD;
|
|
||||||
|
|
||||||
@Translator(packet = ClientboundLevelChunkWithLightPacket.class)
|
@Translator(packet = ClientboundLevelChunkWithLightPacket.class)
|
||||||
public class JavaLevelChunkWithLightTranslator extends PacketTranslator<ClientboundLevelChunkWithLightPacket> {
|
public class JavaLevelChunkWithLightTranslator extends PacketTranslator<ClientboundLevelChunkWithLightPacket> {
|
||||||
@ -56,17 +82,211 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
|
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
|
// Ensure that, if the player is using lower world heights, the position is not offset
|
||||||
int yOffset = session.getChunkCache().getChunkMinY();
|
int yOffset = session.getChunkCache().getChunkMinY();
|
||||||
|
|
||||||
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, chunk, yOffset);
|
// Temporarily stores compound tags of Bedrock-only block entities
|
||||||
GeyserChunkSection[] sections = chunkData.sections();
|
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;
|
||||||
|
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
|
// Find highest section
|
||||||
int sectionCount = sections.length - 1;
|
sectionCount = sections.length - 1;
|
||||||
while (sectionCount >= 0 && sections[sectionCount] == null) {
|
while (sectionCount >= 0 && sections[sectionCount] == null) {
|
||||||
sectionCount--;
|
sectionCount--;
|
||||||
}
|
}
|
||||||
@ -81,19 +301,16 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
||||||
size += 1; // Border blocks
|
size += 1; // Border blocks
|
||||||
size += 1; // Extra data length (always 0)
|
size += 1; // Extra data length (always 0)
|
||||||
size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
|
size += bedrockBlockEntities.length * 64; // Conservative estimate of 64 bytes per tile entity
|
||||||
|
|
||||||
// Allocate output buffer
|
// Allocate output buffer
|
||||||
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||||
byte[] payload;
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < sectionCount; i++) {
|
for (int i = 0; i < sectionCount; i++) {
|
||||||
GeyserChunkSection section = sections[i];
|
GeyserChunkSection section = sections[i];
|
||||||
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
|
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we're dealing with Bedrock chunk sections
|
// 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;
|
int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4;
|
||||||
for (int i = 0; i < sectionCount; i++) {
|
for (int i = 0; i < sectionCount; i++) {
|
||||||
int biomeYOffset = dimensionOffset + i;
|
int biomeYOffset = dimensionOffset + i;
|
||||||
@ -102,7 +319,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
|
||||||
continue;
|
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
|
// 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
|
// Encode tile entities into buffer
|
||||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||||
for (NbtMap blockEntity : chunkData.blockEntities()) {
|
for (NbtMap blockEntity : bedrockBlockEntities) {
|
||||||
nbtStream.writeTag(blockEntity);
|
nbtStream.writeTag(blockEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,14 +343,16 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
session.getConnector().getLogger().error("IO error while encoding chunk", e);
|
session.getConnector().getLogger().error("IO error while encoding chunk", e);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
|
if (byteBuf != null) {
|
||||||
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||||
levelChunkPacket.setSubChunksLength(sectionCount);
|
levelChunkPacket.setSubChunksLength(sectionCount);
|
||||||
levelChunkPacket.setCachingEnabled(false);
|
levelChunkPacket.setCachingEnabled(false);
|
||||||
levelChunkPacket.setChunkX(chunk.getX());
|
levelChunkPacket.setChunkX(packet.getX());
|
||||||
levelChunkPacket.setChunkZ(chunk.getZ());
|
levelChunkPacket.setChunkZ(packet.getZ());
|
||||||
levelChunkPacket.setData(payload);
|
levelChunkPacket.setData(payload);
|
||||||
session.sendUpstreamPacket(levelChunkPacket);
|
session.sendUpstreamPacket(levelChunkPacket);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.translators.world;
|
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 com.github.steveice10.opennbt.tag.builtin.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
@ -98,16 +100,20 @@ public class BiomeTranslator {
|
|||||||
return bedrockData;
|
return bedrockData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BlockStorage toNewBedrockBiome(GeyserSession session, int[] biomeData, int ySection) {
|
public static BlockStorage toNewBedrockBiome(GeyserSession session, DataPalette biomeData) {
|
||||||
Int2IntMap biomeTranslations = session.getBiomeTranslations();
|
Int2IntMap biomeTranslations = session.getBiomeTranslations();
|
||||||
// As of 1.17.10: the client expects the same format as a chunk but filled with biomes
|
// As of 1.17.10: the client expects the same format as a chunk but filled with biomes
|
||||||
|
// As of 1.18 this is the same as Java Edition
|
||||||
|
|
||||||
|
if (biomeData.getPalette() instanceof SingletonPalette palette) {
|
||||||
|
int biomeId = biomeTranslations.get(palette.idToState(0));
|
||||||
|
return new BlockStorage(biomeId);
|
||||||
|
} else {
|
||||||
BlockStorage storage = new BlockStorage(0);
|
BlockStorage storage = new BlockStorage(0);
|
||||||
|
|
||||||
int biomeY = ySection << 2;
|
|
||||||
int javaOffsetY = biomeY << 4;
|
|
||||||
// Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries
|
// Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries
|
||||||
for (int i = 0; i < 64; i++) {
|
for (int i = 0; i < 64; i++) {
|
||||||
int javaId = biomeData[javaOffsetY | i];
|
int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i));
|
||||||
int x = i & 3;
|
int x = i & 3;
|
||||||
int y = (i >> 4) & 3;
|
int y = (i >> 4) & 3;
|
||||||
int z = (i >> 2) & 3;
|
int z = (i >> 2) & 3;
|
||||||
@ -124,7 +130,7 @@ public class BiomeTranslator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -42,11 +42,7 @@ public abstract class BlockEntityTranslator {
|
|||||||
|
|
||||||
public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState);
|
public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState);
|
||||||
|
|
||||||
public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) {
|
public NbtMap getBlockEntityTag(String id, int x, int y, int z, 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();
|
|
||||||
|
|
||||||
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z);
|
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z);
|
||||||
translateTag(tagBuilder, tag, blockState);
|
translateTag(tagBuilder, tag, blockState);
|
||||||
return tagBuilder.build();
|
return tagBuilder.build();
|
||||||
|
@ -25,33 +25,15 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.network.translators.world.chunk;
|
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.DataPalette;
|
||||||
import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.geysermc.connector.network.session.cache.ChunkCache;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acts as a lightweight version of {@link Chunk} that doesn't store
|
* Acts as a lightweight chunk class that doesn't store biomes, heightmaps or block entities.
|
||||||
* biomes or heightmaps.
|
|
||||||
*/
|
*/
|
||||||
public class GeyserChunk {
|
public record GeyserChunk(@Getter DataPalette[] sections) {
|
||||||
@Getter
|
|
||||||
private final ChunkSection[] sections;
|
|
||||||
|
|
||||||
private GeyserChunk(ChunkSection[] sections) {
|
public static GeyserChunk from(DataPalette[] 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();
|
|
||||||
}
|
|
||||||
return new GeyserChunk(sections);
|
return new GeyserChunk(sections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ import java.util.Map;
|
|||||||
public class BlockEntityUtils {
|
public class BlockEntityUtils {
|
||||||
/**
|
/**
|
||||||
* A list of all block entities that require the Java block state in order to fill out their block entity information.
|
* 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<>();
|
public static final ObjectArrayList<BedrockOnlyBlockEntity> BEDROCK_ONLY_BLOCK_ENTITIES = new ObjectArrayList<>();
|
||||||
|
|
||||||
|
@ -25,42 +25,26 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.utils;
|
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.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.Vector2i;
|
||||||
import com.nukkitx.math.vector.Vector3i;
|
import com.nukkitx.math.vector.Vector3i;
|
||||||
import com.nukkitx.nbt.NbtMap;
|
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
|
||||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||||
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
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.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.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.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;
|
import static org.geysermc.connector.network.translators.world.block.BlockStateValues.JAVA_AIR_ID;
|
||||||
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
@ -73,8 +57,8 @@ public class ChunkUtils {
|
|||||||
/**
|
/**
|
||||||
* The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
|
* The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
|
||||||
*/
|
*/
|
||||||
private static final int MAXIMUM_ACCEPTED_HEIGHT = 256;
|
public static final int MAXIMUM_ACCEPTED_HEIGHT = 256;
|
||||||
private static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384;
|
public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384;
|
||||||
|
|
||||||
public static final byte[] EMPTY_CHUNK_DATA;
|
public static final byte[] EMPTY_CHUNK_DATA;
|
||||||
public static final byte[] EMPTY_BIOME_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);
|
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) {
|
public static void updateChunkPosition(GeyserSession session, Vector3i position) {
|
||||||
Vector2i chunkPos = session.getLastChunkPosition();
|
Vector2i chunkPos = session.getLastChunkPosition();
|
||||||
Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4);
|
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();
|
double coordinateScale = ((DoubleTag) dimensionTag.get("coordinate_scale")).getValue();
|
||||||
session.getWorldBorder().setWorldCoordinateScale(coordinateScale);
|
session.getWorldBorder().setWorldCoordinateScale(coordinateScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ChunkData(GeyserChunkSection[] sections, NbtMap[] blockEntities) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,8 @@ public class DimensionUtils {
|
|||||||
stopSoundPacket.setSoundName("");
|
stopSoundPacket.setSoundName("");
|
||||||
session.sendUpstreamPacket(stopSoundPacket);
|
session.sendUpstreamPacket(stopSoundPacket);
|
||||||
|
|
||||||
// TODO - fix this hack of a fix by sending the final dimension switching logic after chunks have been sent.
|
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
|
||||||
// The client wants chunks sent to it before it can successfully respawn.
|
// The client wants sections sent to it before it can successfully respawn.
|
||||||
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren