From 723cb527d1645e5f86be7c20ef3a32966dcb424b Mon Sep 17 00:00:00 2001 From: emortal Date: Wed, 21 Dec 2022 03:53:27 +0000 Subject: [PATCH] Convert to Java Also improved converter massively (no more OOM) --- README.md | 28 +-- build.gradle.kts | 14 +- gradle.properties | 3 +- src/main/java/dev/emortal/tnt/TNT.java | 176 ++++++++++++++++++ src/main/java/dev/emortal/tnt/TNTChunk.java | 19 ++ .../java/dev/emortal/tnt/TNTExtension.java | 15 ++ src/main/java/dev/emortal/tnt/TNTLoader.java | 146 +++++++++++++++ .../dev/emortal/tnt/source/FileTNTSource.java | 65 +++++++ .../dev/emortal/tnt/source/TNTSource.java | 9 + src/main/kotlin/dev/emortal/tnt/TNT.kt | 139 -------------- src/main/kotlin/dev/emortal/tnt/TNTChunk.kt | 12 -- .../kotlin/dev/emortal/tnt/TNTExtension.kt | 13 -- src/main/kotlin/dev/emortal/tnt/TNTLoader.kt | 132 ------------- .../dev/emortal/tnt/source/FileTNTSource.kt | 31 --- .../dev/emortal/tnt/source/TNTSource.kt | 11 -- 15 files changed, 446 insertions(+), 367 deletions(-) create mode 100644 src/main/java/dev/emortal/tnt/TNT.java create mode 100644 src/main/java/dev/emortal/tnt/TNTChunk.java create mode 100644 src/main/java/dev/emortal/tnt/TNTExtension.java create mode 100644 src/main/java/dev/emortal/tnt/TNTLoader.java create mode 100644 src/main/java/dev/emortal/tnt/source/FileTNTSource.java create mode 100644 src/main/java/dev/emortal/tnt/source/TNTSource.java delete mode 100644 src/main/kotlin/dev/emortal/tnt/TNT.kt delete mode 100644 src/main/kotlin/dev/emortal/tnt/TNTChunk.kt delete mode 100644 src/main/kotlin/dev/emortal/tnt/TNTExtension.kt delete mode 100644 src/main/kotlin/dev/emortal/tnt/TNTLoader.kt delete mode 100644 src/main/kotlin/dev/emortal/tnt/source/FileTNTSource.kt delete mode 100644 src/main/kotlin/dev/emortal/tnt/source/TNTSource.kt diff --git a/README.md b/README.md index 91b67fb..4ffaf29 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ TNT is an experimental world format for Minestom ## Cool stuff - - Very small file size (~80kb in TNT vs ~13mb in Anvil for my lobby) - - Very fast loading times (23ms for my lobby - idk what Anvil is) - - [Converts from Anvil automatically](#anvil-conversion) - - [Could be loaded from databases, like Slime worlds](#tnt-sources) - - Stores block nbt (like sign text) - - Stores cached light from anvil (useful because Minestom doesn't have a light engine yet) +- Designed for small worlds (global palette) +- Very fast loading times (23ms for my lobby - idk what Anvil is) +- Very small file size (~80kb in TNT vs ~13mb in Anvil for my lobby) +- [Converts from Anvil automatically](#anvil-conversion) +- [Could be loaded from databases, like Slime worlds](#tnt-sources) +- Stores block nbt (like sign text) +- Stores cached light from anvil (useful because Minestom doesn't have a light engine yet) Unfortunately does not save entities (yet) as Minestom does not have entity (de)serialisation. @@ -19,18 +20,9 @@ Also does not have world saving yet Creating a Minestom instance ```java -// In Kotlin -val instance = MinecraftServer.getInstanceManager().createInstanceContainer() -val tntLoader = TNTLoader(instance, FileTNTSource(Path.of("path/to/world.tnt"))) -// Shorthand version -val tntLoader = TNTLoader(instance, "path/to/world.tnt") - -instance.chunkLoader = tntLoader - -// In Java InstanceContainer instance = MinecraftServer.getInstanceManager().createInstanceContainer(); -TNTLoader tntLoader = new TNTLoader(instance, FileTNTSource(Path.of("path/to/world.tnt"))); -// Shorthand version +TNTLoader tntLoader = new TNTLoader(instance, new FileTNTSource(Path.of("path/to/world.tnt"))); +// or TNTLoader tntLoader = new TNTLoader(instance, "path/to/world.tnt") instance.setChunkLoader(tntLoader); @@ -56,4 +48,4 @@ TNT worlds can be loaded and saved wherever you want (however only `FileTNTSourc For example, you could make it read from Redis, MongoDB, MySQL or any sort of datastore. -You can do this by overriding `TNTSource` and creating your own source. +You can do this by extending `TNTSource` and creating your own source. diff --git a/build.gradle.kts b/build.gradle.kts index 62930b6..6b08c1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.jetbrains.kotlin.jvm") version "1.7.10" - kotlin("plugin.serialization") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.1.2" `maven-publish` @@ -23,8 +20,7 @@ dependencies { compileOnly("com.github.Minestom:Minestom:d596992c0e") - compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2") - implementation("com.github.luben:zstd-jni:1.5.2-3") + implementation("com.github.luben:zstd-jni:1.5.2-5") } tasks { @@ -43,11 +39,9 @@ tasks { build { dependsOn(shadowJar) } } -val compileKotlin: KotlinCompile by tasks -compileKotlin.kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() - -compileKotlin.kotlinOptions { - freeCompilerArgs = listOf("-Xinline-classes") +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } publishing { diff --git a/gradle.properties b/gradle.properties index 9e9b2e9..ac8003b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ kotlin.code.style=official name=TNT mainClass=TNTExtension -group=dev.emortal.tnt \ No newline at end of file +group=dev.emortal.tnt +version=1.0.0 \ No newline at end of file diff --git a/src/main/java/dev/emortal/tnt/TNT.java b/src/main/java/dev/emortal/tnt/TNT.java new file mode 100644 index 0000000..b342715 --- /dev/null +++ b/src/main/java/dev/emortal/tnt/TNT.java @@ -0,0 +1,176 @@ +package dev.emortal.tnt; + +import com.github.luben.zstd.Zstd; +import dev.emortal.tnt.source.TNTSource; +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.*; +import net.minestom.server.instance.block.Block; +import net.minestom.server.utils.binary.BinaryWriter; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +public class TNT { + + private static Logger LOGGER = LoggerFactory.getLogger("TNT"); + + private static byte[] convertChunk(Chunk chunk) throws IOException { + BinaryWriter writer = new BinaryWriter(); + + writer.writeInt(chunk.getChunkX()); + writer.writeInt(chunk.getChunkZ()); + writer.writeByte((byte) chunk.getMinSection()); + writer.writeByte((byte) chunk.getMaxSection()); +// LOGGER.info("Chunk {} {} min max {} {}", chunk.getChunkX(), chunk.getChunkZ(), chunk.getMinSection(), chunk.getMaxSection()); + + int sectionIter = 0; + for (Section section : chunk.getSections()) { + int airSkip = 0; + boolean needsEnding = false; + + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + Block block = chunk.getBlock(x, y + ((sectionIter + chunk.getMinSection()) * Chunk.CHUNK_SECTION_SIZE), z); + + if (block.compare(Block.AIR)) { + airSkip++; + if (airSkip == 1) { + writer.writeShort((short )0); +// LOGGER.info("Wrote short 0"); + needsEnding = true; + } + + continue; + } + if (airSkip > 0) { + writer.writeInt(airSkip); +// LOGGER.info("Wrote int {}", airSkip); + needsEnding = false; + } + + airSkip = 0; + + writer.writeShort(block.stateId()); +// LOGGER.info("Wrote short {}", block.stateId()); + + NBTCompound nbt = block.nbt(); + writer.writeBoolean(block.hasNbt()); +// LOGGER.info("Wrote bool {}", block.hasNbt()); + if (nbt != null) { + writer.writeNBT("blockNBT", nbt); + } + } + } + } + + // Air skip sometimes isn't written, maybe there is a cleaner way? + if (needsEnding) { + writer.writeInt(airSkip); + } + + writer.writeByteArray(section.getBlockLight()); + writer.writeByteArray(section.getSkyLight()); + + sectionIter++; + } + + byte[] bytes = writer.toByteArray(); +// byte[] compressed = Zstd.compress(bytes); + +// source.save(compressed); + + writer.close(); + writer.flush(); + + return bytes; + } + + public static void convertAnvilToTNT(Path anvilPath, TNTSource source) throws IOException, InterruptedException { + InstanceManager im = MinecraftServer.getInstanceManager(); + + Set mcas = Files.list(anvilPath.resolve("region")).collect(Collectors.toSet()); + + InstanceContainer convertInstance = im.createInstanceContainer(); + AnvilLoader loader = new AnvilLoader(anvilPath); + convertInstance.setChunkLoader(loader); + + CountDownLatch cdl = new CountDownLatch(mcas.size() * 32 * 32); + + ArrayList convertedChunks = new ArrayList<>(); + + for (Path mca : mcas) { + String[] args = mca.getFileName().toString().split("\\."); + int rX = Integer.parseInt(args[1]); + int rZ = Integer.parseInt(args[2]); + +// LOGGER.info("Found mca x{} z{}", rX, rZ); + + for (int x = rX * 32; x < rX * 32 + 32; x++) { + for (int z = rZ * 32; z < rZ * 32 + 32; z++) { + convertInstance.loadChunk(x, z).thenAccept(chunk -> { + + // Ignore chunks that contain no blocks + boolean containsBlocks = false; + for (Section section : chunk.getSections()) { + if (section.blockPalette().count() > 0) { + containsBlocks = true; + break; + } + } + if (!containsBlocks) { + cdl.countDown(); + return; + } + +// LOGGER.info("Using chunk {} {}", chunk.getChunkX(), chunk.getChunkZ()); + + + byte[] converted = new byte[0]; + try { + converted = convertChunk(chunk); + } catch (IOException e) { + e.printStackTrace(); + } + convertedChunks.add(converted); + converted = null; + + // We're now done with this chunk + convertInstance.unloadChunk(chunk); + + cdl.countDown(); + }); + } + } + } + + cdl.await(); + + BinaryWriter writer = new BinaryWriter(); + + writer.writeInt(convertedChunks.size()); + for (byte[] chunk : convertedChunks) { + writer.writeBytes(chunk); + } + +// LOGGER.info("Wrote {} chunks", convertedChunks.size()); + + byte[] bytes = writer.toByteArray(); + byte[] compressed = Zstd.compress(bytes); + + source.save(compressed); + + writer.close(); + writer.flush(); + } + +} diff --git a/src/main/java/dev/emortal/tnt/TNTChunk.java b/src/main/java/dev/emortal/tnt/TNTChunk.java new file mode 100644 index 0000000..f9d0700 --- /dev/null +++ b/src/main/java/dev/emortal/tnt/TNTChunk.java @@ -0,0 +1,19 @@ +package dev.emortal.tnt; + +import net.minestom.server.instance.Section; +import net.minestom.server.instance.batch.ChunkBatch; + +import java.util.Arrays; + +public class TNTChunk { + + public ChunkBatch batch; + public Section[] sections; + public TNTChunk(ChunkBatch batch, int maxSection, int minSection) { + this.sections = new Section[maxSection - minSection]; + Arrays.setAll(sections, (a) -> new Section()); + + this.batch = batch; + } + +} diff --git a/src/main/java/dev/emortal/tnt/TNTExtension.java b/src/main/java/dev/emortal/tnt/TNTExtension.java new file mode 100644 index 0000000..d23d2ad --- /dev/null +++ b/src/main/java/dev/emortal/tnt/TNTExtension.java @@ -0,0 +1,15 @@ +package dev.emortal.tnt; + +import net.minestom.server.extensions.Extension; + +class TNTExtension extends Extension { + @Override + public void initialize() { + + } + + @Override + public void terminate() { + + } +} diff --git a/src/main/java/dev/emortal/tnt/TNTLoader.java b/src/main/java/dev/emortal/tnt/TNTLoader.java new file mode 100644 index 0000000..f183db1 --- /dev/null +++ b/src/main/java/dev/emortal/tnt/TNTLoader.java @@ -0,0 +1,146 @@ +package dev.emortal.tnt; + +import com.github.luben.zstd.Zstd; +import dev.emortal.tnt.source.TNTSource; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.*; +import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockManager; +import net.minestom.server.utils.binary.BinaryReader; +import net.minestom.server.utils.chunk.ChunkUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public final class TNTLoader implements IChunkLoader { + + private static Logger LOGGER = LoggerFactory.getLogger("TNTLoader"); + + private final Instance instance; + private final TNTSource source; + public final Long2ObjectOpenHashMap chunksMap = new Long2ObjectOpenHashMap<>(); + + public TNTLoader(Instance instance, TNTSource source) throws IOException, NBTException { + this.instance = instance; + this.source = source; + + BlockManager blockManager = MinecraftServer.getBlockManager(); + + byte[] byteArray = source.load().readAllBytes(); + byte[] decompressed = Zstd.decompress(byteArray, (int) Zstd.decompressedSize(byteArray)); + BinaryReader reader = new BinaryReader(decompressed); + NBTReader nbtReader = new NBTReader(reader, CompressedProcesser.NONE); + + int chunks = reader.readInt(); +// LOGGER.info("Reading {} chunks", chunks); + + for (int chunkI = 0; chunkI < chunks; chunkI++) { + ChunkBatch batch = new ChunkBatch(); + + int chunkX = reader.readInt(); + int chunkZ = reader.readInt(); + + int minSection = reader.readByte(); + int maxSection = reader.readByte(); + +// LOGGER.info("Load chunk {} {} min max {} {}", chunkX, chunkZ, minSection, maxSection); + + TNTChunk mstChunk = new TNTChunk(batch, maxSection, minSection); + + for (int sectionY = minSection; sectionY < maxSection; sectionY++) { + int airSkip = 0; + Section section = mstChunk.sections[sectionY - minSection]; + + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_X; z++) { + if (airSkip > 0) { + airSkip--; + continue; + } + + short stateId = reader.readShort(); +// LOGGER.info("Read short {}", stateId); + + if (stateId == 0) { + airSkip = reader.readInt() - 1; +// LOGGER.info("Read int {}", airSkip); + continue; + } + + boolean hasNbt = reader.readBoolean(); +// LOGGER.info("Read bool {}", hasNbt); + + Block block; + + if (hasNbt) { + NBT nbt = nbtReader.read(); + + Block b = Block.fromStateId(stateId); + block = b.withHandler(blockManager.getHandlerOrDummy(b.name())).withNbt((NBTCompound) nbt); + } else { + block = Block.fromStateId(stateId); + } + + batch.setBlock(x, y + (sectionY * 16), z, block); + } + } + } + + byte[] blockLights = reader.readByteArray(); + byte[] skyLights = reader.readByteArray(); + section.setBlockLight(blockLights); + section.setSkyLight(skyLights); + } + + chunksMap.put(ChunkUtils.getChunkIndex(chunkX, chunkZ), mstChunk); + } + + reader.close(); + nbtReader.close(); + } + + @Override + public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { + TNTChunk mstChunk = chunksMap.get(ChunkUtils.getChunkIndex(chunkX, chunkZ)); + if(mstChunk == null) + return CompletableFuture.completedFuture(null); + DynamicChunk chunk = new DynamicChunk(instance, chunkX, chunkZ); + + CompletableFuture future = new CompletableFuture<>(); + + // Copy chunk light from mstChunk to the new chunk + chunk.getSections().forEach(it -> { + Section sec = mstChunk.sections[chunk.getSections().indexOf(it)]; + it.setBlockLight(sec.getBlockLight()); + it.setSkyLight(sec.getSkyLight()); + }); + + mstChunk.batch.unsafeApply(instance, chunk, future::complete); + + return future; + } + + @Override + public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { + return CompletableFuture.completedFuture(null); + } + + @Override + public boolean supportsParallelLoading() { + return true; + } + + @Override + public boolean supportsParallelSaving() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/dev/emortal/tnt/source/FileTNTSource.java b/src/main/java/dev/emortal/tnt/source/FileTNTSource.java new file mode 100644 index 0000000..3154a87 --- /dev/null +++ b/src/main/java/dev/emortal/tnt/source/FileTNTSource.java @@ -0,0 +1,65 @@ +package dev.emortal.tnt.source; + +import dev.emortal.tnt.TNT; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileTNTSource implements TNTSource { + + private static Logger LOGGER = LoggerFactory.getLogger("FileTNTSource"); + + private final Path path; + + public FileTNTSource(Path path) { + this.path = path; + } + + @Override + public InputStream load() { + if (!Files.exists(path)) { // No world folder + if (Files.isDirectory(path.getParent().resolve(getNameWithoutExtension(path.getFileName().toString())))) { + LOGGER.info("Path is an anvil world. Converting! (This might take a bit)"); + + try { + TNT.convertAnvilToTNT(path.getParent().resolve(getNameWithoutExtension(path.getFileName().toString())), new FileTNTSource(path)); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + LOGGER.info("Converted!"); + } else { + LOGGER.error("Path doesn't exist!"); + } + } + + try { + return Files.newInputStream(path); + } catch (IOException e) { + e.printStackTrace(); + return InputStream.nullInputStream(); + } + } + + @Override + public void save(byte[] bytes) { + try { + Files.write(path, bytes); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getNameWithoutExtension(String path) { + int dotIndex = path.lastIndexOf('.'); + return (dotIndex == -1) ? path : path.substring(0, dotIndex); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/emortal/tnt/source/TNTSource.java b/src/main/java/dev/emortal/tnt/source/TNTSource.java new file mode 100644 index 0000000..972c57e --- /dev/null +++ b/src/main/java/dev/emortal/tnt/source/TNTSource.java @@ -0,0 +1,9 @@ +package dev.emortal.tnt.source; + +import java.io.InputStream; + +public interface TNTSource { + InputStream load(); + + void save(byte[] bytes); +} diff --git a/src/main/kotlin/dev/emortal/tnt/TNT.kt b/src/main/kotlin/dev/emortal/tnt/TNT.kt deleted file mode 100644 index df03132..0000000 --- a/src/main/kotlin/dev/emortal/tnt/TNT.kt +++ /dev/null @@ -1,139 +0,0 @@ -package dev.emortal.tnt - -import com.github.luben.zstd.Zstd -import dev.emortal.tnt.source.TNTSource -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import net.minestom.server.MinecraftServer -import net.minestom.server.instance.Chunk -import net.minestom.server.instance.block.Block -import net.minestom.server.utils.binary.BinaryWriter -import org.slf4j.LoggerFactory -import java.nio.file.Files -import java.nio.file.Path -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CountDownLatch -import java.util.stream.Collectors -import kotlin.io.path.nameWithoutExtension - -object TNT { - - private val LOGGER = LoggerFactory.getLogger(TNT::class.java) - - val tntScope = CoroutineScope(Dispatchers.IO) - - /** - * Saves a .TNT world from chunks - */ - fun createTNTFile(chunks: Collection, tntSource: TNTSource) { - val writer = BinaryWriter() - - writer.writeInt(chunks.size) - - chunks.forEach { - writer.writeInt(it.chunkX) - writer.writeInt(it.chunkZ) - - writer.writeByte(it.minSection.toByte()) - writer.writeByte(it.maxSection.toByte()) - - it.sections.forEachIndexed { sectionI, section -> - var airSkip = 0 - var needsEnding = false - for (x in 0 until Chunk.CHUNK_SIZE_X) { - for (y in 0 until Chunk.CHUNK_SECTION_SIZE) { - for (z in 0 until Chunk.CHUNK_SIZE_X) { - val block = it.getBlock(x, y + ((sectionI + it.minSection) * Chunk.CHUNK_SECTION_SIZE), z) - - if (block == Block.AIR) { - airSkip++ - if (airSkip == 1) { - writer.writeShort(0) - needsEnding = true - } - - continue - } - if (airSkip > 0) { - writer.writeInt(airSkip) - needsEnding = false - } - - airSkip = 0 - - val nbt = block.nbt() - - writer.writeShort(block.stateId()) - writer.writeBoolean(block.hasNbt()) - if (nbt != null) { - writer.writeNBT("blockNBT", nbt) - } - } - } - } - - // Air skip sometimes isn't written, maybe there is a cleaner way? - if (needsEnding) { - writer.writeInt(airSkip) - } - - writer.writeByteArray(section.blockLight) - writer.writeByteArray(section.skyLight) - } - } - - val bytes: ByteArray = writer.toByteArray() - val compressed = Zstd.compress(bytes) - - tntSource.save(compressed) - - writer.close() - writer.flush() - } - - /** - * Converts an anvil folder to a TNT file - * - * Source support should be added - */ - fun convertAnvilToTNT(pathToAnvil: Path, tntSaveSource: TNTSource) { - val instanceManager = MinecraftServer.getInstanceManager() - - val mcaFiles = Files.list(pathToAnvil.resolve("region")).collect( - Collectors.toSet()) - - val convertInstance = instanceManager.createInstanceContainer() - val loader = ConversionAnvilLoader(pathToAnvil) - convertInstance.chunkLoader = loader - - val countDownLatch = CountDownLatch((mcaFiles.size) * 32 * 32) // each MCA file contains 32 chunks - val chunks: MutableSet = ConcurrentHashMap.newKeySet() - - mcaFiles.forEach { - val args = it.nameWithoutExtension.split(".").takeLast(2) - val rX = args[0].toInt() - val rZ = args[1].toInt() - - for (x in rX * 32 until rX * 32 + 32) { - for (z in rZ * 32 until rZ * 32 + 32) { - convertInstance.loadChunk(x, z).thenAcceptAsync { - // Ignore chunks that contain no blocks - if (it.sections.any { it.blockPalette().count() > 0 }) chunks.add(it) - - countDownLatch.countDown() - } - } - } - } - - val before = System.nanoTime() - countDownLatch.await() - println("Took ${(System.nanoTime() - before) / 1_000_000}ms to convert") - - // TODO: make source independant - createTNTFile(chunks, tntSaveSource) - - instanceManager.unregisterInstance(convertInstance) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/dev/emortal/tnt/TNTChunk.kt b/src/main/kotlin/dev/emortal/tnt/TNTChunk.kt deleted file mode 100644 index 43c0610..0000000 --- a/src/main/kotlin/dev/emortal/tnt/TNTChunk.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.emortal.tnt - -import net.minestom.server.instance.Section -import net.minestom.server.instance.batch.ChunkBatch - -class TNTChunk(val chunkBatch: ChunkBatch, val maxSection: Int, val minSection: Int) { - - val sections: List
= Array(maxSection - minSection) { - Section() - }.toList() - -} \ No newline at end of file diff --git a/src/main/kotlin/dev/emortal/tnt/TNTExtension.kt b/src/main/kotlin/dev/emortal/tnt/TNTExtension.kt deleted file mode 100644 index e464757..0000000 --- a/src/main/kotlin/dev/emortal/tnt/TNTExtension.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.emortal.tnt - -import net.minestom.server.extensions.Extension - -class TNTExtension : Extension() { - override fun initialize() { - - } - - override fun terminate() { - - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/emortal/tnt/TNTLoader.kt b/src/main/kotlin/dev/emortal/tnt/TNTLoader.kt deleted file mode 100644 index 9c5eadb..0000000 --- a/src/main/kotlin/dev/emortal/tnt/TNTLoader.kt +++ /dev/null @@ -1,132 +0,0 @@ -package dev.emortal.tnt - -import com.github.luben.zstd.Zstd -import dev.emortal.tnt.source.FileTNTSource -import dev.emortal.tnt.source.TNTSource -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap -import net.minestom.server.MinecraftServer -import net.minestom.server.coordinate.Point -import net.minestom.server.coordinate.Pos -import net.minestom.server.instance.Chunk -import net.minestom.server.instance.DynamicChunk -import net.minestom.server.instance.IChunkLoader -import net.minestom.server.instance.Instance -import net.minestom.server.instance.batch.ChunkBatch -import net.minestom.server.instance.block.Block -import net.minestom.server.utils.binary.BinaryReader -import net.minestom.server.utils.chunk.ChunkUtils -import org.jglrxavpok.hephaistos.nbt.CompressedProcesser -import org.jglrxavpok.hephaistos.nbt.NBTCompound -import org.jglrxavpok.hephaistos.nbt.NBTReader -import org.slf4j.LoggerFactory -import java.nio.file.Path -import java.util.concurrent.CompletableFuture - - -val LOGGER = LoggerFactory.getLogger(TNTLoader::class.java) - -class TNTLoader(val instance: Instance, val tntSource: TNTSource, val offset: Point = Pos.ZERO) : IChunkLoader { - - constructor(instance: Instance, path: String, offset: Point = Pos.ZERO) : this(instance, FileTNTSource(Path.of(path)), offset) - - private val chunksMap = Long2ObjectOpenHashMap() - - init { - val blockManager = MinecraftServer.getBlockManager() - - val byteArray = tntSource.load().readAllBytes() - val decompressed = Zstd.decompress(byteArray, Zstd.decompressedSize(byteArray).toInt()) - val reader = BinaryReader(decompressed) - val nbtReader = NBTReader(reader, CompressedProcesser.NONE) - - val chunks = reader.readInt() - - for (chunkI in 0 until chunks) { - val batch = ChunkBatch() - - val chunkX = reader.readInt() - val chunkZ = reader.readInt() - - val minSection = reader.readByte().toInt() - val maxSection = reader.readByte().toInt() - - val mstChunk = TNTChunk(batch, maxSection, minSection) - - for (sectionY in minSection until maxSection) { - var airSkip = 0 - val section = mstChunk.sections[sectionY - minSection] - - for (x in 0 until Chunk.CHUNK_SIZE_X) { - for (y in 0 until Chunk.CHUNK_SECTION_SIZE) { - for (z in 0 until Chunk.CHUNK_SIZE_X) { - if (airSkip > 0) { - airSkip-- - continue - } - - val stateId = reader.readShort() - - if (stateId == 0.toShort()) { - airSkip = reader.readInt() - 1 - continue - } - - val hasNbt = reader.readBoolean() - - val block = if (hasNbt) { - val nbt = nbtReader.read() - - Block.fromStateId(stateId)!! - .let { - it.withHandler(blockManager.getHandlerOrDummy(it.name())) - } - .withNbt(nbt as NBTCompound) - } else { - Block.fromStateId(stateId)!! - } - - // TODO: fix X and Z offset - batch.setBlock(x/* + offset.blockX()*/, y + (sectionY * 16) + offset.blockY(), z/* + offset.blockZ()*/, block) - } - } - } - - val blockLights = reader.readByteArray() - val skyLights = reader.readByteArray() - section.blockLight = blockLights - section.skyLight = skyLights - } - - chunksMap[ChunkUtils.getChunkIndex(chunkX, chunkZ)] = mstChunk - } - - reader.close() - nbtReader.close() - } - - override fun loadChunk(instance: Instance, chunkX: Int, chunkZ: Int): CompletableFuture { - val mstChunk = chunksMap[ChunkUtils.getChunkIndex(chunkX, chunkZ)] ?: return CompletableFuture.completedFuture(null) - val chunk = DynamicChunk(instance, chunkX, chunkZ) - - val future = CompletableFuture() - - // Copy chunk light from mstChunk to the new chunk - chunk.sections.forEachIndexed { i, it -> - val sec = mstChunk.sections[i] - it.blockLight = sec.blockLight - it.skyLight = sec.skyLight - } - mstChunk.chunkBatch.apply(instance, chunk) { future.complete(chunk) } - - instance.saveChunksToStorage() - - return future - } - - override fun saveChunk(chunk: Chunk): CompletableFuture { - return CompletableFuture.completedFuture(null) - } - - override fun supportsParallelLoading(): Boolean = true - -} \ No newline at end of file diff --git a/src/main/kotlin/dev/emortal/tnt/source/FileTNTSource.kt b/src/main/kotlin/dev/emortal/tnt/source/FileTNTSource.kt deleted file mode 100644 index 50404b9..0000000 --- a/src/main/kotlin/dev/emortal/tnt/source/FileTNTSource.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.emortal.tnt.source - -import dev.emortal.tnt.LOGGER -import dev.emortal.tnt.TNT -import java.io.InputStream -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.nameWithoutExtension - -class FileTNTSource(val path: Path) : TNTSource { - override fun load(): InputStream { - if (!Files.exists(path)) { - // No world folder - - if (Files.isDirectory(path.parent.resolve(path.nameWithoutExtension))) { - LOGGER.info("Path is an anvil world. Converting! (This might take a bit)") - - TNT.convertAnvilToTNT(path.parent.resolve(path.nameWithoutExtension), FileTNTSource(path)) - LOGGER.info("Converted!") - } else { - LOGGER.error("Path doesn't exist!") - } - } - - return Files.newInputStream(path) - } - - override fun save(bytes: ByteArray) { - Files.write(path, bytes) - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/emortal/tnt/source/TNTSource.kt b/src/main/kotlin/dev/emortal/tnt/source/TNTSource.kt deleted file mode 100644 index 43a0df5..0000000 --- a/src/main/kotlin/dev/emortal/tnt/source/TNTSource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.emortal.tnt.source - -import java.io.InputStream - -interface TNTSource { - - fun load(): InputStream - - fun save(bytes: ByteArray) - -} \ No newline at end of file