From fc14d01e787f816534c913b10b5974d88d54b54c Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 20 Jun 2021 20:12:07 +0200 Subject: [PATCH] Initial anvil support --- .../minestom/server/instance/AnvilLoader.java | 140 ++++++++++++++++++ .../server/instance/InstanceContainer.java | 4 +- .../minestom/server/instance/block/Block.java | 9 ++ 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/minestom/server/instance/AnvilLoader.java diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java new file mode 100644 index 000000000..45ed2cff1 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -0,0 +1,140 @@ +package net.minestom.server.instance; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.block.Block; +import net.minestom.server.utils.chunk.ChunkCallback; +import net.minestom.server.world.biomes.Biome; +import net.minestom.server.world.biomes.BiomeManager; +import org.jglrxavpok.hephaistos.mca.*; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class AnvilLoader implements IChunkLoader { + private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class); + private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager(); + private static final Biome BIOME = Biome.PLAINS; + + private final Map alreadyLoaded = new ConcurrentHashMap<>(); + private final Path path; + + public AnvilLoader(Path path) { + this.path = path; + } + + public AnvilLoader(String path) { + this(Path.of(path)); + } + + @Override + public boolean loadChunk(Instance instance, int chunkX, int chunkZ, ChunkCallback callback) { + LOGGER.debug("Attempt loading at {} {}", chunkX, chunkZ); + try { + Chunk chunk = loadMCA(instance, chunkX, chunkZ, callback); + return chunk != null; + } catch (IOException | AnvilException e) { + e.printStackTrace(); + } + return false; + } + + private Chunk loadMCA(Instance instance, int chunkX, int chunkZ, ChunkCallback callback) throws IOException, AnvilException { + RegionFile mcaFile = getMCAFile(chunkX, chunkZ); + if (mcaFile != null) { + ChunkColumn fileChunk = mcaFile.getChunk(chunkX, chunkZ); + if (fileChunk != null) { + Biome[] biomes; + if (fileChunk.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) { + int[] fileChunkBiomes = fileChunk.getBiomes(); + biomes = new Biome[fileChunkBiomes.length]; + for (int i = 0; i < fileChunkBiomes.length; i++) { + final int id = fileChunkBiomes[i]; + biomes[i] = Objects.requireNonNullElse(BIOME_MANAGER.getById(id), BIOME); + } + } else { + biomes = new Biome[1024]; // TODO don't hardcode + Arrays.fill(biomes, BIOME); + } + Chunk chunk = new DynamicChunk(instance, biomes, chunkX, chunkZ); + placeBlocks(chunk, fileChunk); + loadTileEntities(chunk, fileChunk); + if (callback != null) { + callback.accept(chunk); + } + return chunk; + } + } + return null; + } + + private RegionFile getMCAFile(int chunkX, int chunkZ) { + int regionX = CoordinatesKt.chunkToRegion(chunkX); + int regionZ = CoordinatesKt.chunkToRegion(chunkZ); + return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> { + try { + final Path regionPath = path.resolve("region").resolve(n); + if (!Files.exists(regionPath)) { + return null; + } + return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ); + } catch (IOException | AnvilException e) { + e.printStackTrace(); + return null; + } + }); + } + + private void loadTileEntities(Chunk loadedChunk, ChunkColumn fileChunk) { + for (NBTCompound te : fileChunk.getTileEntities()) { + final String tileEntityID = te.getString("id"); + final int x = te.getInt("x") + loadedChunk.getChunkX() * 16; + final int y = te.getInt("y"); + final int z = te.getInt("z") + loadedChunk.getChunkZ() * 16; + if (tileEntityID != null) { + // TODO load BlockHandler and place + } + } + } + + private void placeBlocks(Chunk chunk, ChunkColumn fileChunk) { + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + for (int y = 0; y < 256; y++) { // TODO don't hardcode height + try { + // TODO: are there block entities here? + final BlockState blockState = fileChunk.getBlockState(x, y, z); + Block block = Block.fromNamespaceId(blockState.getName()); + if (block == null) { + // Invalid block + continue; + } + final var properties = blockState.getProperties(); + if (!properties.isEmpty()) { + block = block.withProperties(properties); + } + chunk.setBlock(x, y, z, block); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + // TODO: find a way to unload MCAFiles when an entire region is unloaded + + @Override + public void saveChunk(Chunk chunk, Runnable callback) { + // TODO + callback.run(); + } +} diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 524ca5cc4..ff58426dc 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -89,8 +89,8 @@ public class InstanceContainer extends Instance { // Set the default chunk supplier using DynamicChunk setChunkSupplier(DynamicChunk::new); - // Set the default chunk loader which use the instance's StorageLocation and ChunkSupplier to save and load chunks - setChunkLoader(new MinestomBasicChunkLoader(this)); + // Set the default chunk loader which use the Anvil format + setChunkLoader(new AnvilLoader("world")); // Get instance data from the saved data if a StorageLocation is defined if (storageLocation != null) { diff --git a/src/main/java/net/minestom/server/instance/block/Block.java b/src/main/java/net/minestom/server/instance/block/Block.java index 00e650626..b6b44fa80 100644 --- a/src/main/java/net/minestom/server/instance/block/Block.java +++ b/src/main/java/net/minestom/server/instance/block/Block.java @@ -31,6 +31,15 @@ public interface Block extends ProtocolObject, TagReadable, BlockConstants { return withProperty(property.getName(), value.toString()); } + @Contract(pure = true) + default @NotNull Block withProperties(@NotNull Map<@NotNull String, @NotNull String> properties) { + Block block = this; + for (var entry : properties.entrySet()) { + block = block.withProperty(entry.getKey(), entry.getValue()); + } + return block; + } + @Contract(pure = true) @NotNull Block withNbt(@Nullable NBTCompound compound);