diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index e6c66ecb4..37c73e52e 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -322,7 +322,7 @@ public Level remove(Object key) { private Watchdog watchdog; - private DB nameLookup; + public DB nameLookup; private PlayerDataSerializer playerDataSerializer; @@ -2142,12 +2142,10 @@ public CompoundTag getOfflinePlayerData(UUID uuid, boolean create) { return getOfflinePlayerDataInternal(uuid.toString(), true, create); } - @Deprecated public CompoundTag getOfflinePlayerData(String name) { return getOfflinePlayerData(name, false); } - @Deprecated public CompoundTag getOfflinePlayerData(String name, boolean create) { Optional<UUID> uuid = lookupName(name); return getOfflinePlayerDataInternal(uuid.map(UUID::toString).orElse(name), true, create); diff --git a/src/main/java/cn/nukkit/convert/BlockEntityConvert.java b/src/main/java/cn/nukkit/convert/BlockEntityConvert.java new file mode 100644 index 000000000..84efb013a --- /dev/null +++ b/src/main/java/cn/nukkit/convert/BlockEntityConvert.java @@ -0,0 +1,37 @@ +package cn.nukkit.convert; + +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +public class BlockEntityConvert { + public static void convertInventory(CompoundTag root) { + ListTag<CompoundTag> items = root.getList("Items", CompoundTag.class); + ListTag<CompoundTag> result = new ListTag<>(); + for (var nbt : items.getAll()) { + int id = nbt.getShort("id"); + int slot = nbt.getByte("Slot"); + int count = nbt.getByte("Count"); + int damage = nbt.getShort("Damage"); + + var newTag = new CompoundTag(); + Item item = Item.get(id); + String namespaceId = item.getNamespaceId(); + newTag.putByte("Count", count) + .putShort("Damage", damage); + newTag.putString("Name", namespaceId); + newTag.putByte("Slot", slot); + if (item.hasCompoundTag()) { + newTag.putCompound("tag", item.getNamedTag()); + } + if (item.getBlockUnsafe() != null) { + newTag.putCompound("Block", NBTIO.putBlockHelper(item.getBlockUnsafe())); + } + result.add(newTag); + } + if (result.size() != 0) { + root.putList("Items", result); + } + } +} diff --git a/src/main/java/cn/nukkit/convert/Convert.java b/src/main/java/cn/nukkit/convert/Convert.java index 340f24b2c..24e78b519 100644 --- a/src/main/java/cn/nukkit/convert/Convert.java +++ b/src/main/java/cn/nukkit/convert/Convert.java @@ -6,13 +6,10 @@ import cn.nukkit.convert.task.ConvertTask; import cn.nukkit.lang.PluginI18n; import cn.nukkit.level.DimensionData; -import cn.nukkit.level.GameRule; import cn.nukkit.level.Level; import cn.nukkit.level.format.anvil.Anvil; import cn.nukkit.level.format.generic.BaseRegionLoader; -import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.types.GameType; -import cn.nukkit.utils.Logger; import lombok.extern.slf4j.Slf4j; import java.io.File; @@ -20,7 +17,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; @Slf4j public class Convert { @@ -29,13 +29,16 @@ public class Convert { public static ForkJoinPool THREAD_POOL_EXECUTOR = (ForkJoinPool) Executors.newWorkStealingPool(); public static void start() { + PlayerDataConvert.start(); + log.info("convert player data complete!"); + File file = new File("worlds", world + "/region"); Level level = Server.getInstance().getLevelByName(world); Anvil levelProvider = (Anvil) level.requireProvider(); try { DimensionData dimensionData = levelProvider.getDimensionData(); - String path = "output/" + level.getName(); + String path = "output/worlds/" + level.getName(); LevelDat build = LevelDat.builder().spawnPoint(level.getSpawnLocation().asBlockVector3()).randomSeed(level.getSeed()).name(level.getName()).gameRules(level.getGameRules()) .gameType(GameType.SURVIVAL).build(); LevelDBStorage levelDBStorage = new LevelDBStorage(path); @@ -62,6 +65,7 @@ public static void start() { task.get(); } levelDBStorage.close(); + log.info("All region is complete!"); } catch (IOException | InterruptedException | ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/main/java/cn/nukkit/convert/PlayerDataConvert.java b/src/main/java/cn/nukkit/convert/PlayerDataConvert.java new file mode 100644 index 000000000..591cc0f45 --- /dev/null +++ b/src/main/java/cn/nukkit/convert/PlayerDataConvert.java @@ -0,0 +1,175 @@ +package cn.nukkit.convert; + +import cn.nukkit.Server; +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import com.google.common.collect.HashBiMap; +import org.iq80.leveldb.CompressionType; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; +import org.iq80.leveldb.impl.Iq80DBFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; + +public class PlayerDataConvert { + static DB db; + + + public static void start() { + Server server = Server.getInstance(); + File output = new File(server.getDataPath(), "output/players"); + if (!output.exists()) { + output.mkdirs(); + } + try { + db = Iq80DBFactory.factory.open(output, new Options() + .createIfMissing(true) + .compressionType(CompressionType.ZLIB_RAW)); + } catch (IOException e) { + throw new RuntimeException(e); + } + DB oldDB = Server.getInstance().nameLookup; + HashBiMap<String, UUID> name2uuid = HashBiMap.create(); + File file = new File(server.getDataPath() + "players"); + for (var f : Objects.requireNonNull(file.listFiles(fi -> fi.getName().endsWith(".dat")))) { + String sUuid = f.getName().replace(".dat", ""); + UUID uuid = UUID.fromString(sUuid); + ByteBuffer buffer = ByteBuffer.allocate(16); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + byte[] v = buffer.array(); + for (java.util.Map.Entry<byte[], byte[]> entry : oldDB) { + if (Arrays.equals(entry.getValue(), v)) { + String s = new String(entry.getKey(), StandardCharsets.UTF_8); + name2uuid.put(s, uuid); + break; + } + } + } + for (var f : Objects.requireNonNull(file.listFiles(fi -> fi.getName().endsWith(".dat")))) { + UUID uuid = UUID.fromString(f.getName().replace(".dat", "")); + CompoundTag offlinePlayerData = server.getOfflinePlayerData(uuid, false); + convertInventory(offlinePlayerData); + String s = name2uuid.inverse().get(uuid); + + ByteBuffer buffer = ByteBuffer.allocate(16); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + byte[] v = buffer.array(); + + db.put(s.getBytes(StandardCharsets.UTF_8), v); + try { + db.put(v, NBTIO.writeGZIPCompressed(offlinePlayerData, ByteOrder.BIG_ENDIAN)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + try { + db.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void convertInventory(CompoundTag root) { + ListTag<CompoundTag> inventory = root.getList("Inventory", CompoundTag.class); + ListTag<CompoundTag> result = new ListTag<>(); + CompoundTag offHand = null; + for (var nbt : inventory.getAll()) { + int id = nbt.getShort("id"); + int slot = nbt.getByte("Slot"); + int count = nbt.getByte("Count"); + int damage = nbt.getShort("Damage"); + + if (slot >= 9 && slot < 45) { + Item item = Item.get(id); + String namespaceId = item.getNamespaceId(); + CompoundTag newTag = new CompoundTag() + .putByte("Count", count) + .putShort("Damage", damage); + newTag.putString("Name", namespaceId); + newTag.putByte("Slot", slot - 9); + if (item.hasCompoundTag()) { + newTag.putCompound("tag", item.getNamedTag()); + } + if (item.getBlockUnsafe() != null) { + newTag.putCompound("Block", NBTIO.putBlockHelper(item.getBlockUnsafe())); + } + result.add(newTag); + } else if (slot < 104 && slot >= 100) {//armor + Item item = Item.get(id); + String namespaceId = item.getNamespaceId(); + CompoundTag newTag = new CompoundTag() + .putByte("Count", count) + .putShort("Damage", damage); + newTag.putString("Name", namespaceId); + newTag.putByte("Slot", slot - 64); + if (item.hasCompoundTag()) { + newTag.putCompound("tag", item.getNamedTag()); + } + if (item.getBlockUnsafe() != null) { + newTag.putCompound("Block", NBTIO.putBlockHelper(item.getBlockUnsafe())); + } + result.add(newTag); + } else if (slot == -106) {//offhand + offHand = new CompoundTag(); + Item item = Item.get(id); + String namespaceId = item.getNamespaceId(); + offHand.putByte("Count", count) + .putShort("Damage", damage); + offHand.putString("Name", namespaceId); + offHand.putByte("Slot", 0); + if (item.hasCompoundTag()) { + offHand.putCompound("tag", item.getNamedTag()); + } + if (item.getBlockUnsafe() != null) { + offHand.putCompound("Block", NBTIO.putBlockHelper(item.getBlockUnsafe())); + } + } + } + if (offHand != null) { + root.putCompound("OffInventory", offHand); + } + if (result.size() != 0) { + root.putList("Inventory", result); + } + + ListTag<CompoundTag> enderItems = root.getList("EnderItems", CompoundTag.class); + ListTag<CompoundTag> resultEnderItems = new ListTag<>(); + for (var nbt : enderItems.getAll()) { + int id = nbt.getShort("id"); + int slot = nbt.getByte("Slot"); + int count = nbt.getByte("Count"); + int damage = nbt.getShort("Damage"); + + if (slot >= 0 && slot < 27) { + Item item = Item.get(id); + String namespaceId = item.getNamespaceId(); + CompoundTag newTag = new CompoundTag() + .putByte("Count", count) + .putShort("Damage", damage); + newTag.putString("Name", namespaceId); + newTag.putByte("Slot", slot); + if (item.hasCompoundTag()) { + newTag.putCompound("tag", item.getNamedTag()); + } + if (item.getBlockUnsafe() != null) { + newTag.putCompound("Block", NBTIO.putBlockHelper(item.getBlockUnsafe())); + } + resultEnderItems.add(newTag); + } + } + if (resultEnderItems.size() != 0) { + root.putList("EnderItems", resultEnderItems); + } + } +} diff --git a/src/main/java/cn/nukkit/convert/leveldb/LevelDBChunkSerializer.java b/src/main/java/cn/nukkit/convert/leveldb/LevelDBChunkSerializer.java index ae1112101..76f8190bd 100644 --- a/src/main/java/cn/nukkit/convert/leveldb/LevelDBChunkSerializer.java +++ b/src/main/java/cn/nukkit/convert/leveldb/LevelDBChunkSerializer.java @@ -4,6 +4,7 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockstate.BlockState; import cn.nukkit.blockstate.BlockStateRegistry; +import cn.nukkit.convert.BlockEntityConvert; import cn.nukkit.convert.palette.Palette; import cn.nukkit.entity.Entity; import cn.nukkit.level.DimensionData; @@ -57,6 +58,7 @@ static int index(int x, int y, int z) { //serialize chunk section private void serializeBlock(WriteBatch writeBatch, Chunk chunk, DimensionData dimensionData) { ChunkSection[] sections = chunk.getSections(); + int minSubY = dimensionData.getMinHeight() >> 4; for (var section : sections) { if (section == null) { continue; @@ -66,6 +68,7 @@ private void serializeBlock(WriteBatch writeBatch, Chunk chunk, DimensionData di buffer.writeByte(9); buffer.writeByte(2); int y = section.getY(); + y += minSubY; buffer.writeByte(y); BlockStateRegistry.Registration registration = BlockStateRegistry.getRegistration(BlockState.AIR); CompoundTag originalBlock = registration.originalBlock; @@ -138,6 +141,7 @@ private void serializeTileAndEntity(WriteBatch writeBatch, Chunk chunk, Dimensio else { for (BlockEntity blockEntity : blockEntities) { blockEntity.saveNBT(); + BlockEntityConvert.convertInventory(blockEntity.namedTag); NBTIO.write(blockEntity.namedTag, bufStream, ByteOrder.LITTLE_ENDIAN); } writeBatch.put(key, Utils.convertByteBuf2Array(tileBuffer)); diff --git a/src/main/java/cn/nukkit/convert/task/ConvertTask.java b/src/main/java/cn/nukkit/convert/task/ConvertTask.java index 30d155f93..b1b4f460a 100644 --- a/src/main/java/cn/nukkit/convert/task/ConvertTask.java +++ b/src/main/java/cn/nukkit/convert/task/ConvertTask.java @@ -41,6 +41,7 @@ protected boolean exec() { if (regionLoader.chunkExists(i, j)) { try { Chunk chunk = (Chunk) regionLoader.readChunk(i, j); + chunk.initChunk(); levelDBStorage.writeChunk(chunk, dimensionData); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java index 93d98790b..7dca7a828 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java @@ -71,6 +71,9 @@ public String toSNBT() { @Override public String toSNBT(int space) { + if (this.data.length == 0) { + return "[B;]"; + } StringBuilder builder = new StringBuilder("[B; "); for (int i = 0; i < this.data.length - 1; i++) { builder.append(data[i]).append("b, ");