Skip to content

Commit

Permalink
Add support for 1.18.30 (#1976)
Browse files Browse the repository at this point in the history
* Initial support for 1.18.30

* Fix typos in DimensionEnum
  • Loading branch information
Alemiz112 authored Apr 20, 2022
1 parent efb15c7 commit 5a48d2c
Show file tree
Hide file tree
Showing 14 changed files with 999 additions and 820 deletions.
24 changes: 24 additions & 0 deletions src/main/java/cn/nukkit/level/DimensionData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cn.nukkit.level;

import lombok.Builder;
import lombok.Data;

@Data
public class DimensionData {
private final int dimensionId;
private final int minHeight;
private final int maxHeight;
private final int height;

public DimensionData(int dimensionId, int minHeight, int maxHeight) {
this.dimensionId = dimensionId;
this.minHeight = minHeight;
this.maxHeight = maxHeight;

int height = maxHeight - minHeight;
if (minHeight <= 0 && maxHeight > 0) {
height += 1; // 0 y coordinate counts too
}
this.height = height;
}
}
26 changes: 26 additions & 0 deletions src/main/java/cn/nukkit/level/DimensionEnum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cn.nukkit.level;

public enum DimensionEnum {
OVERWORLD(new DimensionData(Level.DIMENSION_OVERWORLD, -64, 319)),
NETHER(new DimensionData(Level.DIMENSION_NETHER, 0, 127)),
END(new DimensionData(Level.DIMENSION_THE_END, 0, 255));

private final DimensionData dimensionData;

DimensionEnum(DimensionData dimensionData) {
this.dimensionData = dimensionData;
}

public DimensionData getDimensionData() {
return this.dimensionData;
}

public static DimensionData getDataFromId(int dimension) {
for (DimensionEnum value : values()) {
if (value.getDimensionData().getDimensionId() == dimension) {
return value.getDimensionData();
}
}
return null;
}
}
18 changes: 11 additions & 7 deletions src/main/java/cn/nukkit/level/Level.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public Generator init() {

private long levelCurrentTick = 0;

private int dimension;
private DimensionData dimensionData;

public GameRules gameRules;

Expand Down Expand Up @@ -409,7 +409,7 @@ public void setTickRate(int tickRate) {

public void initLevel() {
Generator generator = generators.get();
this.dimension = generator.getDimension();
this.dimensionData = generator.getDimensionData();
this.gameRules = this.provider.getGamerules();

this.server.getLogger().info("Preparing start region for level \"" + this.getFolderName() + "\"");
Expand Down Expand Up @@ -567,11 +567,11 @@ public void addParticle(Particle particle, Collection<Player> players) {
}

public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect) {
this.addParticleEffect(pos, particleEffect, -1, this.dimension, (Player[]) null);
this.addParticleEffect(pos, particleEffect, -1, this.getDimension(), (Player[]) null);
}

public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId) {
this.addParticleEffect(pos, particleEffect, uniqueEntityId, this.dimension, (Player[]) null);
this.addParticleEffect(pos, particleEffect, uniqueEntityId, this.getDimension(), (Player[]) null);
}

public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId, int dimensionId) {
Expand Down Expand Up @@ -764,7 +764,7 @@ public void doTick(int currentTick) {
}

// Tick Weather
if (this.dimension != DIMENSION_NETHER && this.dimension != DIMENSION_THE_END && gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) {
if (this.getDimension() != DIMENSION_NETHER && this.getDimension() != DIMENSION_THE_END && gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) {
this.rainTime--;
if (this.rainTime <= 0) {
if (!this.setRaining(!this.raining)) {
Expand Down Expand Up @@ -3417,8 +3417,12 @@ public void sendWeather(Collection<Player> players) {
this.sendWeather(players.toArray(new Player[0]));
}

public DimensionData getDimensionData() {
return this.dimensionData;
}

public int getDimension() {
return dimension;
return this.dimensionData.getDimensionId();
}

public boolean canBlockSeeSky(Vector3 pos) {
Expand Down Expand Up @@ -3537,7 +3541,7 @@ public int getUpdateLCG() {
}

public boolean createPortal(Block target) {
if (this.dimension == DIMENSION_THE_END) return false;
if (this.getDimension() == DIMENSION_THE_END) return false;
int maxPortalSize = 23;
final int targX = target.getFloorX();
final int targY = target.getFloorY();
Expand Down
81 changes: 5 additions & 76 deletions src/main/java/cn/nukkit/level/format/anvil/Anvil.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
package cn.nukkit.level.format.anvil;

import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.blockentity.BlockEntitySpawnable;
import cn.nukkit.level.Level;
import cn.nukkit.level.biome.Biome;
import cn.nukkit.level.format.FullChunk;
import cn.nukkit.level.format.generic.BaseFullChunk;
import cn.nukkit.level.format.generic.BaseLevelProvider;
import cn.nukkit.level.format.generic.BaseRegionLoader;
import cn.nukkit.level.format.generic.serializer.NetworkChunkSerializer;
import cn.nukkit.level.generator.Generator;
import cn.nukkit.level.util.PalettedBlockStorage;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.scheduler.AsyncTask;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.ChunkException;
import cn.nukkit.utils.ThreadCache;
import it.unimi.dsi.fastutil.objects.ObjectIterator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -115,78 +110,12 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException {
}

long timestamp = chunk.getChanges();

byte[] blockEntities = new byte[0];

if (!chunk.getBlockEntities().isEmpty()) {
List<CompoundTag> tagList = new ArrayList<>();

for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
if (blockEntity instanceof BlockEntitySpawnable) {
tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound());
}
}

try {
blockEntities = NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

int count = 0;
cn.nukkit.level.format.ChunkSection[] sections = chunk.getSections();
for (int i = sections.length - 1; i >= 0; i--) {
if (!sections[i].isEmpty()) {
count = i + 1;
break;
}
}

// In 1.18 3D biome palettes were introduced. However, current world format
// used internally doesn't support them, so we need to convert from legacy 2D
byte[] biomePalettes = this.convert2DBiomesTo3D(chunk);

BinaryStream stream = ThreadCache.binaryStream.get().reset();
// Build up 4 SubChunks for the extended negative height
for (int i = 0; i < EXTENDED_NEGATIVE_SUB_CHUNKS; i++) {
stream.putByte((byte) 8); // SubChunk version
stream.putByte((byte) 0); // 0 layers
}

for (int i = 0; i < count; i++) {
sections[i].writeTo(stream);
}

stream.put(biomePalettes);
stream.putByte((byte) 0); // Border blocks
stream.put(blockEntities);
this.getLevel().chunkRequestCallback(timestamp, x, z, EXTENDED_NEGATIVE_SUB_CHUNKS + count, stream.getBuffer());
BiConsumer<BinaryStream, Integer> callback = (stream, subchunks) ->
this.getLevel().chunkRequestCallback(timestamp, x, z, subchunks, stream.getBuffer());
NetworkChunkSerializer.serialize(chunk, callback, this.level.getDimensionData());
return null;
}

private byte[] convert2DBiomesTo3D(BaseFullChunk chunk) {
PalettedBlockStorage palette = PalettedBlockStorage.createWithDefaultState(Biome.getBiomeIdOrCorrect(chunk.getBiomeId(0, 0)));
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int biomeId = Biome.getBiomeIdOrCorrect(chunk.getBiomeId(x, z));
for (int y = 0; y < 16; y++) {
palette.setBlock(x, y, z, biomeId);
}
}
}

BinaryStream stream = ThreadCache.binaryStream.get().reset();
palette.writeTo(stream);
byte[] bytes = stream.getBuffer();
stream.reset();

for (int i = 0; i < 25; i++) {
stream.put(bytes);
}
return stream.getBuffer();
}

private int lastPosition = 0;

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package cn.nukkit.level.format.generic.serializer;

import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.blockentity.BlockEntitySpawnable;
import cn.nukkit.level.DimensionData;
import cn.nukkit.level.Level;
import cn.nukkit.level.biome.Biome;
import cn.nukkit.level.format.ChunkSection;
import cn.nukkit.level.format.generic.BaseChunk;
import cn.nukkit.level.format.generic.BaseFullChunk;
import cn.nukkit.level.util.PalettedBlockStorage;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.ThreadCache;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

import java.io.IOException;
import java.nio.ByteOrder;
import java.util.List;
import java.util.function.BiConsumer;

public class NetworkChunkSerializer {

private static final int EXTENDED_NEGATIVE_SUB_CHUNKS = 4;

private static final byte[] negativeSubChunks;

static {
// Build up 4 SubChunks for the extended negative height
BinaryStream stream = new BinaryStream();
for (int i = 0; i < EXTENDED_NEGATIVE_SUB_CHUNKS; i++) {
stream.putByte((byte) 8); // SubChunk version
stream.putByte((byte) 0); // 0 layers
}
negativeSubChunks = stream.getBuffer();
}

public static void serialize(BaseChunk chunk, BiConsumer<BinaryStream, Integer> callback, DimensionData dimensionData) {
byte[] blockEntities;
if (chunk.getBlockEntities().isEmpty()) {
blockEntities = new byte[0];
} else {
blockEntities = serializeEntities(chunk);
}

int subChunkCount = 0;
ChunkSection[] sections = chunk.getSections();
for (int i = sections.length - 1; i >= 0; i--) {
if (!sections[i].isEmpty()) {
subChunkCount = i + 1;
break;
}
}

int maxDimensionSections = dimensionData.getHeight() >> 4;
subChunkCount = Math.min(maxDimensionSections, subChunkCount);

// In 1.18 3D biome palettes were introduced. However, current world format
// used internally doesn't support them, so we need to convert from legacy 2D
byte[] biomePalettes = convert2DBiomesTo3D(chunk, maxDimensionSections);
BinaryStream stream = ThreadCache.binaryStream.get().reset();

// Overworld has negative coordinates, but we currently do not support them
int writtenSections = subChunkCount;
if (dimensionData.getDimensionId() == Level.DIMENSION_OVERWORLD && subChunkCount < maxDimensionSections) {
stream.put(negativeSubChunks);
writtenSections += EXTENDED_NEGATIVE_SUB_CHUNKS;
}

for (int i = 0; i < subChunkCount; i++) {
sections[i].writeTo(stream);
}

stream.put(biomePalettes);
stream.putByte((byte) 0); // Border blocks
stream.put(blockEntities);
callback.accept(stream, writtenSections);
}

private static byte[] serializeEntities(BaseChunk chunk) {
List<CompoundTag> tagList = new ObjectArrayList<>();
for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
if (blockEntity instanceof BlockEntitySpawnable) {
tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound());
}
}

try {
return NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static byte[] convert2DBiomesTo3D(BaseFullChunk chunk, int sections) {
PalettedBlockStorage palette = PalettedBlockStorage.createWithDefaultState(Biome.getBiomeIdOrCorrect(chunk.getBiomeId(0, 0)));
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int biomeId = Biome.getBiomeIdOrCorrect(chunk.getBiomeId(x, z));
for (int y = 0; y < 16; y++) {
palette.setBlock(x, y, z, biomeId);
}
}
}

BinaryStream stream = ThreadCache.binaryStream.get().reset();
palette.writeTo(stream);
byte[] bytes = stream.getBuffer();
stream.reset();

for (int i = 0; i < sections; i++) {
stream.put(bytes);
}
return stream.getBuffer();
}
}
11 changes: 11 additions & 0 deletions src/main/java/cn/nukkit/level/generator/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import cn.nukkit.block.BlockID;
import cn.nukkit.level.ChunkManager;
import cn.nukkit.level.DimensionData;
import cn.nukkit.level.DimensionEnum;
import cn.nukkit.level.Level;
import cn.nukkit.math.NukkitRandom;
import cn.nukkit.math.Vector3;
Expand All @@ -20,6 +22,15 @@ public abstract class Generator implements BlockID {

public abstract int getId();

public DimensionData getDimensionData() {
DimensionData dimensionData = DimensionEnum.getDataFromId(this.getDimension());
if (dimensionData == null) {
dimensionData = DimensionEnum.OVERWORLD.getDimensionData();
}
return dimensionData;
}

@Deprecated
public int getDimension() {
return Level.DIMENSION_OVERWORLD;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/cn/nukkit/network/protocol/AddPlayerPacket.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cn.nukkit.network.protocol;

import cn.nukkit.Server;
import cn.nukkit.entity.data.EntityMetadata;
import cn.nukkit.item.Item;
import cn.nukkit.utils.Binary;
Expand Down Expand Up @@ -34,6 +35,7 @@ public byte pid() {
public float pitch;
public float yaw;
public Item item;
public int gameType = Server.getInstance().getGamemode();
public EntityMetadata metadata = new EntityMetadata();
//public EntityLink links = new EntityLink[0];
public String deviceId = "";
Expand All @@ -58,6 +60,7 @@ public void encode() {
this.putLFloat(this.yaw); //TODO headrot
this.putLFloat(this.yaw);
this.putSlot(this.item);
this.putVarInt(this.gameType);
this.put(Binary.writeMetadata(this.metadata));
this.putUnsignedVarInt(0); //TODO: Adventure settings
this.putUnsignedVarInt(0);
Expand Down
Loading

0 comments on commit 5a48d2c

Please sign in to comment.