Load level.dat nbt on instance init

This commit is contained in:
TheMode 2021-08-09 01:16:51 +02:00
parent 0c52c9eb30
commit cb7bccf26c
5 changed files with 71 additions and 14 deletions

View File

@ -5,15 +5,14 @@ import net.minestom.server.exception.ExceptionManager;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.mca.*;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTList;
import org.jglrxavpok.hephaistos.nbt.NBTTypes;
import org.jglrxavpok.hephaistos.nbt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,6 +21,7 @@ import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@ -37,10 +37,12 @@ public class AnvilLoader implements IChunkLoader {
private final Map<String, RegionFile> alreadyLoaded = new ConcurrentHashMap<>();
private final Path path;
private final Path levelPath;
private final Path regionPath;
public AnvilLoader(@NotNull Path path) {
this.path = path;
this.levelPath = path.resolve("level.dat");
this.regionPath = path.resolve("region");
}
@ -48,6 +50,20 @@ public class AnvilLoader implements IChunkLoader {
this(Path.of(path));
}
@Override
public void loadInstance(@NotNull Instance instance) {
if (!Files.exists(levelPath)) {
return;
}
try (var reader = new NBTReader(Files.newInputStream(levelPath))) {
final NBTCompound tag = (NBTCompound) reader.read();
Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING);
instance.setTag(Tag.NBT, tag);
} catch (IOException | NBTException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
}
@Override
public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
LOGGER.debug("Attempt loading at {} {}", chunkX, chunkZ);
@ -116,17 +132,16 @@ public class AnvilLoader implements IChunkLoader {
private void loadBlocks(Chunk chunk, ChunkColumn fileChunk) {
for (var section : fileChunk.getSections()) {
if (section.getEmpty()) {
continue;
}
if (section.getEmpty()) continue;
final int yOffset = Chunk.CHUNK_SECTION_SIZE * section.getY();
for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) {
for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) {
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
try {
final BlockState blockState = section.get(x, y, z);
chunk.setBlock(x, y + yOffset, z,
Block.fromNamespaceId(blockState.getName()).withProperties(blockState.getProperties()));
final Block block = Objects.requireNonNull(Block.fromNamespaceId(blockState.getName()))
.withProperties(blockState.getProperties());
chunk.setBlock(x, y + yOffset, z, block);
} catch (Exception e) {
EXCEPTION_MANAGER.handleException(e);
}
@ -167,8 +182,20 @@ public class AnvilLoader implements IChunkLoader {
}
}
// TODO: find a way to unload MCAFiles when an entire region is unloaded
@Override
public @NotNull CompletableFuture<Void> saveInstance(@NotNull Instance instance) {
final var nbt = instance.getTag(Tag.NBT);
if (nbt == null) {
// Instance has no data
return AsyncUtils.VOID_FUTURE;
}
try (NBTWriter writer = new NBTWriter(Files.newOutputStream(levelPath))) {
writer.writeNamed("", nbt);
} catch (IOException e) {
e.printStackTrace();
}
return AsyncUtils.VOID_FUTURE;
}
@Override
public @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk) {

View File

@ -18,6 +18,14 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public interface IChunkLoader {
/**
* Loads instance data from the loader.
*
* @param instance the instance to retrieve the data from
*/
default void loadInstance(@NotNull Instance instance) {
}
/**
* Loads a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied.
*
@ -28,6 +36,10 @@ public interface IChunkLoader {
*/
@NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ);
default @NotNull CompletableFuture<Void> saveInstance(@NotNull Instance instance) {
return AsyncUtils.VOID_FUTURE;
}
/**
* Saves a {@link Chunk} with an optional callback for when it is done.
*

View File

@ -227,6 +227,17 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
*/
public abstract @Nullable Chunk getChunk(int chunkX, int chunkZ);
/**
* Saves the current instance tags.
* <p>
* Warning: only the global instance data will be saved, not chunks.
* You would need to call {@link #saveChunksToStorage()} too.
*
* @return the future called once the instance data has been saved
*/
@ApiStatus.Experimental
public abstract @NotNull CompletableFuture<Void> saveInstance();
/**
* Saves a {@link Chunk} to permanent storage.
*

View File

@ -41,9 +41,6 @@ import java.util.function.Consumer;
*/
public class InstanceContainer extends Instance {
private static final String UUID_KEY = "uuid";
private static final String DATA_KEY = "data";
// the shared instances assigned to this instance
private final List<SharedInstance> sharedInstances = new CopyOnWriteArrayList<>();
@ -85,6 +82,7 @@ public class InstanceContainer extends Instance {
// Set the default chunk loader which use the Anvil format
setChunkLoader(new AnvilLoader("world"));
this.chunkLoader.loadInstance(this);
}
@Override
@ -258,6 +256,11 @@ public class InstanceContainer extends Instance {
}
}
@Override
public @NotNull CompletableFuture<Void> saveInstance() {
return chunkLoader.saveInstance(this);
}
@Override
public @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk) {
return chunkLoader.saveChunk(chunk);

View File

@ -15,7 +15,6 @@ import java.util.concurrent.CompletableFuture;
* entities are separated.
*/
public class SharedInstance extends Instance {
private final InstanceContainer instanceContainer;
public SharedInstance(@NotNull UUID uniqueId, @NotNull InstanceContainer instanceContainer) {
@ -59,6 +58,11 @@ public class SharedInstance extends Instance {
return instanceContainer.getChunk(chunkX, chunkZ);
}
@Override
public @NotNull CompletableFuture<Void> saveInstance() {
return instanceContainer.saveInstance();
}
@Override
public @NotNull CompletableFuture<Void> saveChunkToStorage(@NotNull Chunk chunk) {
return instanceContainer.saveChunkToStorage(chunk);