From 4c9cfbfdf981d47cf37c12df0363733bc72f4275 Mon Sep 17 00:00:00 2001 From: dfsek Date: Tue, 21 Dec 2021 22:35:07 -0700 Subject: [PATCH] cli serialization --- .../com/dfsek/terra/cli/NBTSerializable.java | 5 + .../java/com/dfsek/terra/cli/TerraCLI.java | 21 +++ .../dfsek/terra/cli/block/CLIBlockState.java | 9 + .../terra/cli/handle/CLIWorldHandle.java | 4 + .../com/dfsek/terra/cli/world/CLIWorld.java | 159 +++++++++++++++++- .../com/dfsek/terra/cli/world/Region.java | 36 +++- .../dfsek/terra/cli/world/chunk/CLIChunk.java | 45 ++++- 7 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 platforms/cli/src/main/java/com/dfsek/terra/cli/NBTSerializable.java diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/NBTSerializable.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/NBTSerializable.java new file mode 100644 index 000000000..0c05a1652 --- /dev/null +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/NBTSerializable.java @@ -0,0 +1,5 @@ +package com.dfsek.terra.cli; + +public interface NBTSerializable { + T serialize(); +} diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/TerraCLI.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/TerraCLI.java index 26cfb006b..a72787446 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/TerraCLI.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/TerraCLI.java @@ -3,9 +3,15 @@ package com.dfsek.terra.cli; import com.dfsek.terra.api.config.ConfigPack; import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent; +import com.dfsek.terra.api.util.vector.Vector2Int; +import com.dfsek.terra.cli.world.CLIWorld; + +import net.querz.mca.MCAUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; + public final class TerraCLI { private static final Logger LOGGER = LoggerFactory.getLogger(TerraCLI.class); @@ -17,7 +23,22 @@ public final class TerraCLI { platform.getEventManager().callEvent(new PlatformInitializationEvent()); ConfigPack generate = platform.getConfigRegistry().get("OVERWORLD").orElseThrow(); // TODO: make this a cli argument + + CLIWorld world = new CLIWorld(1, 2, 384, -64, generate); + world.generate(); + world.serialize().forEach(mcaFile -> { + Vector2Int pos = mcaFile.getLeft(); + String name = MCAUtil.createNameFromRegionLocation(pos.getX(), pos.getZ()); + LOGGER.info("Writing region ({}, {}) to {}", pos.getX(), pos.getZ(), name); + mcaFile.getRight().cleanupPalettesAndBlockStates(); + + try { + MCAUtil.write(mcaFile.getRight(), name); + } catch(IOException e) { + e.printStackTrace(); + } + }); } } diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/block/CLIBlockState.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/block/CLIBlockState.java index bd8f5258c..96dea0c84 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/block/CLIBlockState.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/block/CLIBlockState.java @@ -4,11 +4,14 @@ import com.dfsek.terra.api.block.BlockType; import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.block.state.properties.Property; +import net.querz.nbt.tag.CompoundTag; + public class CLIBlockState implements BlockState { private final String value; private final CLIBlockType type; private final boolean isAir; + private final CompoundTag nbt; public CLIBlockState(String value) { this.value = value; @@ -18,6 +21,8 @@ public class CLIBlockState implements BlockState { this.type = new CLIBlockType(value); } this.isAir = value.startsWith("minecraft:air"); + this.nbt = new CompoundTag(); + this.nbt.putString("Name", value); } @Override @@ -59,4 +64,8 @@ public class CLIBlockState implements BlockState { public boolean isAir() { return isAir; } + + public CompoundTag getNbt() { + return nbt; + } } diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/handle/CLIWorldHandle.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/handle/CLIWorldHandle.java index bb692639d..4ec2dc619 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/handle/CLIWorldHandle.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/handle/CLIWorldHandle.java @@ -25,4 +25,8 @@ public class CLIWorldHandle implements WorldHandle { public @NotNull EntityType getEntity(@NotNull String id) { return null; } + + public static CLIBlockState getAIR() { + return AIR; + } } diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/CLIWorld.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/CLIWorld.java index 2509608dc..4752bb4e9 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/CLIWorld.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/CLIWorld.java @@ -1,5 +1,12 @@ package com.dfsek.terra.cli.world; +import com.dfsek.terra.api.util.generic.pair.Pair; +import com.dfsek.terra.api.util.vector.Vector2Int; +import com.dfsek.terra.api.world.chunk.generation.ProtoWorld; +import com.dfsek.terra.cli.NBTSerializable; + +import com.dfsek.terra.cli.world.chunk.CLIChunk; + import net.jafama.FastMath; import com.dfsek.terra.api.block.entity.BlockEntity; @@ -10,11 +17,25 @@ import com.dfsek.terra.api.entity.EntityType; import com.dfsek.terra.api.util.vector.Vector3; import com.dfsek.terra.api.world.ServerWorld; import com.dfsek.terra.api.world.biome.generation.BiomeProvider; -import com.dfsek.terra.api.world.chunk.Chunk; import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator; +import net.querz.mca.MCAFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class CLIWorld implements ServerWorld { +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + + +public class CLIWorld implements ServerWorld, NBTSerializable>> { + private static final Logger LOGGER = LoggerFactory.getLogger(CLIWorld.class); private static final int regionBlocks = 32 * 16; private final Region[] regions; private final int size; @@ -24,6 +45,9 @@ public class CLIWorld implements ServerWorld { private final ChunkGenerator chunkGenerator; private final BiomeProvider biomeProvider; private final ConfigPack pack; + private final AtomicInteger amount = new AtomicInteger(0); + + private final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()-1); public CLIWorld(int size, long seed, @@ -31,13 +55,50 @@ public class CLIWorld implements ServerWorld { int minHeight, ConfigPack pack) { this.size = size; - this.regions = new Region[size * size]; - this.seed = seed; this.maxHeight = maxHeight; this.minHeight = minHeight; + this.seed = seed; this.chunkGenerator = pack.getGeneratorProvider().newInstance(pack); this.biomeProvider = pack.getBiomeProvider(); this.pack = pack; + + + this.regions = new Region[(size + 1) * (size + 1)]; + for(int x = 0; x < size + 1; x++) { + for(int z = 0; z < size + 1; z++) { + regions[x + z * (size + 1)] = new Region(this, x, z); + } + } + } + + public void generate() { + int sizeChunks = size * 32; + List> futures = new ArrayList<>(); + final long start = System.nanoTime(); + for(int x = -sizeChunks + 1; x < sizeChunks; x++) { + for(int z = -sizeChunks + 1; z < sizeChunks; z++) { + int finalX = x; + int finalZ = z; + futures.add(executor.submit(() -> { + int num = amount.getAndIncrement(); + long time = System.nanoTime(); + double cps = num / ((double) (time - start) / 1000000000); + LOGGER.info("Generating chunk at ({}, {}), generated {} chunks at {}cps", finalX, finalZ, num, cps); + CLIChunk chunk = getChunkAt(finalX, finalZ); + chunkGenerator.generateChunkData(chunk, this, finalX, finalZ); + CLIProtoWorld protoWorld = new CLIProtoWorld(this, finalX, finalZ); + pack.getStages().forEach(stage -> stage.populate(protoWorld)); + })); + } + } + + for(Future future : futures) { + try { + future.get(); + } catch(InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } } @Override @@ -92,7 +153,9 @@ public class CLIWorld implements ServerWorld { } @Override - public Chunk getChunkAt(int x, int z) { + public CLIChunk getChunkAt(int x, int z) { + x += (size + 1) * 16; + z += (size + 1) * 16; return regions[FastMath.floorDiv(x, regionBlocks) + regionBlocks * FastMath.floorDiv(z, regionBlocks)] .get(FastMath.floorMod(FastMath.floorDiv(x, 16), 32), FastMath.floorMod(FastMath.floorDiv(z, 16), 32)); } @@ -137,4 +200,90 @@ public class CLIWorld implements ServerWorld { public Entity spawnEntity(double x, double y, double z, EntityType entityType) { return null; } + + @Override + public Stream> serialize() { + return Arrays.stream(regions).map(region -> Pair.of(Vector2Int.of(region.getX(), region.getZ()), region.serialize())); + } + + private static final class CLIProtoWorld implements ProtoWorld { + private final CLIWorld delegate; + private final int x, z; + + private CLIProtoWorld(CLIWorld delegate, int x, int z) { + this.delegate = delegate; + this.x = x; + this.z = z; + } + + @Override + public Object getHandle() { + return this; + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + return delegate.getBlockState(x, y, z); + } + + @Override + public BlockEntity getBlockEntity(int x, int y, int z) { + return delegate.getBlockEntity(x, y, z); + } + + @Override + public long getSeed() { + return delegate.seed; + } + + @Override + public int getMaxHeight() { + return delegate.maxHeight; + } + + @Override + public int getMinHeight() { + return delegate.minHeight; + } + + @Override + public ChunkGenerator getGenerator() { + return delegate.chunkGenerator; + } + + @Override + public BiomeProvider getBiomeProvider() { + return delegate.biomeProvider; + } + + @Override + public ConfigPack getPack() { + return delegate.pack; + } + + @Override + public void setBlockState(int x, int y, int z, BlockState data, boolean physics) { + delegate.setBlockState(x, y, z, data, physics); + } + + @Override + public Entity spawnEntity(double x, double y, double z, EntityType entityType) { + return delegate.spawnEntity(x, y, z, entityType); + } + + @Override + public int centerChunkX() { + return x; + } + + @Override + public int centerChunkZ() { + return z; + } + + @Override + public ServerWorld getWorld() { + return delegate; + } + } } diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/Region.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/Region.java index 3d32c2ba3..3390b466c 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/Region.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/Region.java @@ -1,16 +1,23 @@ package com.dfsek.terra.cli.world; +import com.dfsek.terra.cli.NBTSerializable; import com.dfsek.terra.cli.world.chunk.CLIChunk; +import net.querz.mca.MCAFile; +import net.querz.mca.MCAUtil; -public class Region { + +public class Region implements NBTSerializable { private final CLIChunk[] chunks; + private final int x, z; - public Region(CLIWorld world) { + public Region(CLIWorld world, int x, int z) { + this.x = x; + this.z = z; CLIChunk[] chunks = new CLIChunk[32 * 32]; - for(int x = 0; x < 32; x++) { - for(int z = 0; z < 32; z++) { - chunks[x * z * 32] = new CLIChunk(x, z, world); + for(int cx = 0; cx < 32; cx++) { + for(int cz = 0; cz < 32; cz++) { + chunks[cx + cz * 32] = new CLIChunk(cx, cz, world); } } this.chunks = chunks; @@ -19,4 +26,23 @@ public class Region { public CLIChunk get(int x, int z) { return chunks[x + z*32]; } + + @Override + public MCAFile serialize() { + MCAFile mcaFile = new MCAFile(x, z); + for(int cx = 0; cx < 32; cx++) { + for(int cz = 0; cz < 32; cz++) { + mcaFile.setChunk(cx, cz, chunks[cx + cz * 32].serialize()); + } + } + return mcaFile; + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } } diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/chunk/CLIChunk.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/chunk/CLIChunk.java index 90193d526..6c5381066 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/world/chunk/CLIChunk.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/world/chunk/CLIChunk.java @@ -4,26 +4,37 @@ import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.world.ServerWorld; import com.dfsek.terra.api.world.chunk.Chunk; +import com.dfsek.terra.api.world.chunk.generation.ProtoChunk; +import com.dfsek.terra.cli.NBTSerializable; import com.dfsek.terra.cli.block.CLIBlockState; +import com.dfsek.terra.cli.handle.CLIWorldHandle; import com.dfsek.terra.cli.world.CLIWorld; +import net.querz.mca.MCAFile; +import net.querz.nbt.tag.CompoundTag; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.dfsek.terra.cli.handle.CLIWorldHandle.getAIR; -public class CLIChunk implements Chunk { +public class CLIChunk implements Chunk, ProtoChunk, NBTSerializable { private final int x; private final int z; private final CLIBlockState[][][] blocks; private final int minHeight; + private final int maxHeight; private final CLIWorld world; public CLIChunk(int x, int z, CLIWorld world) { this.x = x; this.z = z; this.minHeight = world.getMinHeight(); + this.maxHeight = world.getMaxHeight(); this.world = world; - this.blocks= new CLIBlockState[16][16][world.getMaxHeight() - minHeight]; + this.blocks= new CLIBlockState[16][16][maxHeight - minHeight]; } @Override @@ -37,8 +48,10 @@ public class CLIChunk implements Chunk { } @Override - public @NotNull BlockState getBlock(int x, int y, int z) { - return blocks[x][z][y - minHeight]; + public @NotNull CLIBlockState getBlock(int x, int y, int z) { + CLIBlockState blockState = blocks[x][z][y - minHeight]; + if(blockState == null) return getAIR(); + return blockState; } @Override @@ -55,4 +68,28 @@ public class CLIChunk implements Chunk { public ServerWorld getWorld() { return world; } + + @Override + public net.querz.mca.Chunk serialize() { + net.querz.mca.Chunk chunk = net.querz.mca.Chunk.newChunk(); + for(int x = 0; x < blocks.length; x++) { + for(int z = 0; z < blocks[x].length; z++) { + for(int y = 0; y < blocks[z][z].length; y++) { + CLIBlockState blockState = blocks[x][z][y]; + if(blockState == null) { + blockState = CLIWorldHandle.getAIR(); + } + int yi = y + minHeight; + if(yi < 0 || yi >= 256) continue; + chunk.setBlockStateAt(x, yi, z, blockState.getNbt(), false); + } + } + } + return chunk; + } + + @Override + public int getMaxHeight() { + return maxHeight; + } }