diff --git a/demo/src/main/java/net/minestom/demo/MainDemo.java b/demo/src/main/java/net/minestom/demo/MainDemo.java index 1f972a03c..1bc8221f8 100644 --- a/demo/src/main/java/net/minestom/demo/MainDemo.java +++ b/demo/src/main/java/net/minestom/demo/MainDemo.java @@ -1,5 +1,7 @@ package net.minestom.demo; +import demo.commands.GamemodeCommand; +import net.minestom.demo.commands.SaveCommand; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.Player; @@ -28,10 +30,14 @@ public class MainDemo { GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler(); globalEventHandler.addListener(PlayerLoginEvent.class, event -> { final Player player = event.getPlayer(); + player.setPermissionLevel(2); event.setSpawningInstance(instanceContainer); player.setRespawnPoint(new Pos(0, 42, 0)); }); + MinecraftServer.getCommandManager().register(new SaveCommand()); + MinecraftServer.getCommandManager().register(new GamemodeCommand()); + // Start the server on port 25565 minecraftServer.start("0.0.0.0", 25565); } diff --git a/demo/src/main/java/net/minestom/demo/commands/SaveCommand.java b/demo/src/main/java/net/minestom/demo/commands/SaveCommand.java new file mode 100644 index 000000000..6703f8292 --- /dev/null +++ b/demo/src/main/java/net/minestom/demo/commands/SaveCommand.java @@ -0,0 +1,33 @@ +package net.minestom.demo.commands; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandContext; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * A simple shutdown command. + */ +public class SaveCommand extends Command { + + public SaveCommand() { + super("save"); + addSyntax(this::execute); + } + + private void execute(@NotNull CommandSender commandSender, @NotNull CommandContext commandContext) { + for(var instance : MinecraftServer.getInstanceManager().getInstances()) { + CompletableFuture instanceSave = instance.saveInstance().thenCompose(v -> instance.saveChunksToStorage()); + try { + instanceSave.get(); + } catch (InterruptedException | ExecutionException e) { + MinecraftServer.getExceptionManager().handleException(e); + } + } + commandSender.sendMessage("Saving done!"); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d3fb96d3..ad7627ac4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ kotlin = "1.6.10" hydrazine = "1.7.2" dependencyGetter = "v1.0.1" minestomData = "801b8007cf" -hephaistos = "2.3.2" +hephaistos = "2.4.0" jetbrainsAnnotations = "23.0.0" # Terminal / Logging diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java index b66189612..3b22326a8 100644 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -80,7 +80,7 @@ public class AnvilLoader implements IChunkLoader { } private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException { - final RegionFile mcaFile = getMCAFile(chunkX, chunkZ); + final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ); if (mcaFile == null) return CompletableFuture.completedFuture(null); final ChunkColumn fileChunk = mcaFile.getChunk(chunkX, chunkZ); @@ -113,8 +113,9 @@ public class AnvilLoader implements IChunkLoader { loadBlocks(chunk, fileChunk); loadTileEntities(chunk, fileChunk); // Lights - for (var chunkSection : fileChunk.getSections().values()) { - Section section = chunk.getSection(chunkSection.getY()); + for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) { + var section = chunk.getSection(sectionY); + var chunkSection = fileChunk.getSection((byte) sectionY); section.setSkyLight(chunkSection.getSkyLights()); section.setBlockLight(chunkSection.getBlockLights()); } @@ -122,7 +123,7 @@ public class AnvilLoader implements IChunkLoader { return CompletableFuture.completedFuture(chunk); } - private @Nullable RegionFile getMCAFile(int chunkX, int chunkZ) { + private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) { final int regionX = CoordinatesKt.chunkToRegion(chunkX); final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> { @@ -131,7 +132,7 @@ public class AnvilLoader implements IChunkLoader { if (!Files.exists(regionPath)) { return null; } - return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ); + return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY()-1); } catch (IOException | AnvilException e) { EXCEPTION_MANAGER.handleException(e); return null; @@ -219,7 +220,7 @@ public class AnvilLoader implements IChunkLoader { final int chunkZ = chunk.getChunkZ(); RegionFile mcaFile; synchronized (alreadyLoaded) { - mcaFile = getMCAFile(chunkX, chunkZ); + mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ); if (mcaFile == null) { final int regionX = CoordinatesKt.chunkToRegion(chunkX); final int regionZ = CoordinatesKt.chunkToRegion(chunkZ); @@ -263,15 +264,17 @@ public class AnvilLoader implements IChunkLoader { } private void save(Chunk chunk, ChunkColumn chunkColumn) { + chunkColumn.changeVersion(SupportedVersion.Companion.getLatest()); + chunkColumn.setYRange(chunk.getMinSection()*16, chunk.getMaxSection()*16-1); List tileEntities = new ArrayList<>(); chunkColumn.setGenerationStatus(ChunkColumn.GenerationStatus.Full); 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 world height + for (int y = chunkColumn.getMinY(); y < chunkColumn.getMaxY(); y++) { final Block block = chunk.getBlock(x, y, z); // Block chunkColumn.setBlockState(x, y, z, new BlockState(block.name(), block.properties())); - chunkColumn.setBiome(x, 0, z, chunk.getBiome(x, y, z).name().asString()); + chunkColumn.setBiome(x, y, z, chunk.getBiome(x, y, z).name().asString()); // Tile entity final BlockHandler handler = block.handler(); diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 4736bd415..c369dd506 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -44,6 +44,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, protected Instance instance; protected final int chunkX, chunkZ; + protected final int minSection, maxSection; // Options private final boolean shouldGenerate; @@ -64,6 +65,8 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, this.chunkX = chunkX; this.chunkZ = chunkZ; this.shouldGenerate = shouldGenerate; + this.minSection = instance.getDimensionType().getMinY() / CHUNK_SECTION_SIZE; + this.maxSection = (instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight()) / CHUNK_SECTION_SIZE; final EntityTracker tracker = instance.getEntityTracker(); this.viewers.updateTracker(toPosition(), tracker); @@ -181,6 +184,24 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter, return chunkZ; } + /** + * Gets the lowest (inclusive) section Y available in this chunk + * + * @return the lowest (inclusive) section Y available in this chunk + */ + public int getMinSection() { + return minSection; + } + + /** + * Gets the highest (exclusive) section Y available in this chunk + * + * @return the highest (exclusive) section Y available in this chunk + */ + public int getMaxSection() { + return maxSection; + } + /** * Gets the world position of this chunk. * diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 9246c4da5..6be1b376c 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -36,7 +36,6 @@ import static net.minestom.server.utils.chunk.ChunkUtils.toSectionRelativeCoordi */ public class DynamicChunk extends Chunk { - private final int minSection, maxSection; private List
sections; // Key = ChunkUtils#getBlockIndex @@ -49,8 +48,6 @@ public class DynamicChunk extends Chunk { public DynamicChunk(@NotNull Instance instance, int chunkX, int chunkZ) { super(instance, chunkX, chunkZ, true); - this.minSection = instance.getDimensionType().getMinY() / CHUNK_SECTION_SIZE; - this.maxSection = (instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight()) / CHUNK_SECTION_SIZE; var sectionsTemp = new Section[maxSection - minSection]; Arrays.setAll(sectionsTemp, value -> new Section()); this.sections = List.of(sectionsTemp); @@ -144,7 +141,7 @@ public class DynamicChunk extends Chunk { public @NotNull Biome getBiome(int x, int y, int z) { final Section section = getSectionAt(y); final int id = section.biomePalette() - .get(toSectionRelativeCoordinate(x) / 4, y / 4, toSectionRelativeCoordinate(z) / 4); + .get(toSectionRelativeCoordinate(x) / 4, toSectionRelativeCoordinate(y) / 4, toSectionRelativeCoordinate(z) / 4); return MinecraftServer.getBiomeManager().getById(id); } diff --git a/src/main/java/net/minestom/server/world/DimensionType.java b/src/main/java/net/minestom/server/world/DimensionType.java index cf1ed1062..c172dd033 100644 --- a/src/main/java/net/minestom/server/world/DimensionType.java +++ b/src/main/java/net/minestom/server/world/DimensionType.java @@ -209,6 +209,10 @@ public class DimensionType { return height; } + public int getMaxY() { + return getMinY() + getHeight(); + } + public int getLogicalHeight() { return this.logicalHeight; }