From 9947f37a26965985fbb3d102ecdf2456e4047e25 Mon Sep 17 00:00:00 2001 From: pop4959 Date: Thu, 1 Feb 2024 04:17:14 -0800 Subject: [PATCH] Implement new world pattern (#315) --- .../org/popcraft/chunky/GenerationTask.java | 1 + .../popcraft/chunky/command/TrimCommand.java | 22 +-- .../chunky/iterator/ChunkIterator.java | 3 + .../chunky/iterator/ChunkIteratorFactory.java | 3 + .../popcraft/chunky/iterator/PatternType.java | 3 +- .../chunky/iterator/WorldChunkIterator.java | 127 ++++++++++++++++++ .../popcraft/chunky/util/ChunkCoordinate.java | 19 +++ .../org/popcraft/chunky/util/Hilbert.java | 7 + common/src/main/resources/lang/en.json | 1 + 9 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 common/src/main/java/org/popcraft/chunky/iterator/WorldChunkIterator.java diff --git a/common/src/main/java/org/popcraft/chunky/GenerationTask.java b/common/src/main/java/org/popcraft/chunky/GenerationTask.java index 9be82068..693107f7 100644 --- a/common/src/main/java/org/popcraft/chunky/GenerationTask.java +++ b/common/src/main/java/org/popcraft/chunky/GenerationTask.java @@ -118,6 +118,7 @@ private synchronized void update(final int chunkX, final int chunkZ, final boole public void run() { final String poolThreadName = Thread.currentThread().getName(); Thread.currentThread().setName(String.format("Chunky-%s Thread", selection.world().getName())); + chunkIterator.process(); final Semaphore working = new Semaphore(MAX_WORKING_COUNT); final boolean forceLoadExistingChunks = chunky.getConfig().isForceLoadExistingChunks(); startTime.set(System.currentTimeMillis()); diff --git a/common/src/main/java/org/popcraft/chunky/command/TrimCommand.java b/common/src/main/java/org/popcraft/chunky/command/TrimCommand.java index 72cbeb5c..48571aee 100644 --- a/common/src/main/java/org/popcraft/chunky/command/TrimCommand.java +++ b/common/src/main/java/org/popcraft/chunky/command/TrimCommand.java @@ -100,7 +100,7 @@ public void execute(final Sender sender, final CommandArguments arguments) { if (regionPath.isPresent()) { try (final Stream files = Files.list(regionPath.get())) { final List regions = files - .filter(file -> tryRegionCoordinate(file.getFileName().toString()).isPresent()) + .filter(file -> ChunkCoordinate.fromRegionFile(file.getFileName().toString()).isPresent()) .toList(); final long totalRegions = regions.size(); for (final Path region: regions) { @@ -135,7 +135,7 @@ public void execute(final Sender sender, final CommandArguments arguments) { } private int checkRegion(final World world, final String regionFileName, final Shape shape, final boolean inside, final boolean inhabitedTimeCheck, final int inhabitedTime) { - final Optional regionCoordinate = tryRegionCoordinate(regionFileName); + final Optional regionCoordinate = ChunkCoordinate.fromRegionFile(regionFileName); if (regionCoordinate.isEmpty()) { return 0; } @@ -148,24 +148,6 @@ private int checkRegion(final World world, final String regionFileName, final Sh } } - private Optional tryRegionCoordinate(final String regionFileName) { - if (!regionFileName.startsWith("r.")) { - return Optional.empty(); - } - final int extension = regionFileName.indexOf(".mca"); - if (extension < 2) { - return Optional.empty(); - } - final String regionCoordinates = regionFileName.substring(2, extension); - final int separator = regionCoordinates.indexOf('.'); - final Optional regionX = Input.tryInteger(regionCoordinates.substring(0, separator)); - final Optional regionZ = Input.tryInteger(regionCoordinates.substring(separator + 1)); - if (regionX.isPresent() && regionZ.isPresent()) { - return Optional.of(new ChunkCoordinate(regionX.get(), regionZ.get())); - } - return Optional.empty(); - } - private boolean shouldDeleteRegion(final Shape shape, final boolean inside, final int chunkX, final int chunkZ) { for (int offsetX = 0; offsetX < 32; ++offsetX) { for (int offsetZ = 0; offsetZ < 32; ++offsetZ) { diff --git a/common/src/main/java/org/popcraft/chunky/iterator/ChunkIterator.java b/common/src/main/java/org/popcraft/chunky/iterator/ChunkIterator.java index a39fbc71..d14965be 100644 --- a/common/src/main/java/org/popcraft/chunky/iterator/ChunkIterator.java +++ b/common/src/main/java/org/popcraft/chunky/iterator/ChunkIterator.java @@ -8,4 +8,7 @@ public interface ChunkIterator extends Iterator { long total(); String name(); + + default void process() { + } } diff --git a/common/src/main/java/org/popcraft/chunky/iterator/ChunkIteratorFactory.java b/common/src/main/java/org/popcraft/chunky/iterator/ChunkIteratorFactory.java index 07469056..42a023e5 100644 --- a/common/src/main/java/org/popcraft/chunky/iterator/ChunkIteratorFactory.java +++ b/common/src/main/java/org/popcraft/chunky/iterator/ChunkIteratorFactory.java @@ -8,6 +8,9 @@ private ChunkIteratorFactory() { } public static ChunkIterator getChunkIterator(final Selection selection, final long count) { + if (selection.pattern().getType().equals(PatternType.WORLD)) { + return new WorldChunkIterator(selection); + } final String shape = selection.shape(); if (ShapeType.RECTANGLE.equals(shape) || ShapeType.ELLIPSE.equals(shape) || ShapeType.OVAL.equals(shape)) { return new Loop2ChunkIterator(selection, count); diff --git a/common/src/main/java/org/popcraft/chunky/iterator/PatternType.java b/common/src/main/java/org/popcraft/chunky/iterator/PatternType.java index 02d7575c..33193c6b 100644 --- a/common/src/main/java/org/popcraft/chunky/iterator/PatternType.java +++ b/common/src/main/java/org/popcraft/chunky/iterator/PatternType.java @@ -8,8 +8,9 @@ public final class PatternType { public static final String SPIRAL = "spiral"; public static final String CSV = "csv"; public static final String REGION = "region"; + public static final String WORLD = "world"; - public static final List ALL = List.of(CONCENTRIC, LOOP, SPIRAL, CSV, REGION); + public static final List ALL = List.of(CONCENTRIC, LOOP, SPIRAL, CSV, REGION, WORLD); private PatternType() { } diff --git a/common/src/main/java/org/popcraft/chunky/iterator/WorldChunkIterator.java b/common/src/main/java/org/popcraft/chunky/iterator/WorldChunkIterator.java new file mode 100644 index 00000000..95a0bbbe --- /dev/null +++ b/common/src/main/java/org/popcraft/chunky/iterator/WorldChunkIterator.java @@ -0,0 +1,127 @@ +package org.popcraft.chunky.iterator; + +import org.popcraft.chunky.Selection; +import org.popcraft.chunky.nbt.StringTag; +import org.popcraft.chunky.nbt.util.RegionFile; +import org.popcraft.chunky.util.ChunkCoordinate; +import org.popcraft.chunky.util.Hilbert; +import org.popcraft.chunky.util.Parameter; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +public class WorldChunkIterator implements ChunkIterator { + private final int minRegionX; + private final int minRegionZ; + private final int maxRegionX; + private final int maxRegionZ; + private final int minChunkX; + private final int minChunkZ; + private final int maxChunkX; + private final int maxChunkZ; + private final Queue chunks; + private final AtomicLong total = new AtomicLong(); + private final Path savePath; + private final Path regionPath; + private final String name; + + public WorldChunkIterator(final Selection selection) { + final int centerRegionX = selection.centerRegionX(); + final int centerRegionZ = selection.centerRegionZ(); + final int radiusRegionsX = selection.radiusRegionsX(); + final int radiusRegionsZ = selection.radiusRegionsZ(); + this.minRegionX = centerRegionX - radiusRegionsX; + this.minRegionZ = centerRegionZ - radiusRegionsZ; + this.maxRegionX = centerRegionX + radiusRegionsX; + this.maxRegionZ = centerRegionZ + radiusRegionsZ; + final int centerChunkX = selection.centerChunkX(); + final int centerChunkZ = selection.centerChunkZ(); + final int radiusChunksX = selection.radiusChunksX(); + final int radiusChunksZ = selection.radiusChunksZ(); + this.minChunkX = centerChunkX - radiusChunksX; + this.minChunkZ = centerChunkZ - radiusChunksZ; + this.maxChunkX = centerChunkX + radiusChunksX; + this.maxChunkZ = centerChunkZ + radiusChunksZ; + this.chunks = new LinkedList<>(); + final String worldName = selection.world().getName(); + final String saveFile = worldName.substring(worldName.indexOf(':') + 1); + this.savePath = selection.chunky().getConfig().getDirectory().resolve(String.format("%s.csv", saveFile)); + this.regionPath = selection.world().getRegionDirectory().orElse(null); + this.name = Parameter.of(PatternType.CSV, saveFile).toString(); + } + + @Override + public boolean hasNext() { + return !chunks.isEmpty(); + } + + @Override + public ChunkCoordinate next() { + if (chunks.isEmpty()) { + throw new NoSuchElementException(); + } + return chunks.poll(); + } + + @Override + public long total() { + return total.get(); + } + + @Override + public String name() { + return name; + } + + @Override + public void process() { + if (regionPath == null) { + return; + } + final StringBuilder saveData = new StringBuilder(); + try (final Stream files = Files.list(regionPath)) { + final List regions = files + .filter(file -> { + final ChunkCoordinate regionCoordinate = ChunkCoordinate.fromRegionFile(file.getFileName().toString()) + .orElse(null); + return regionCoordinate != null && regionCoordinate.x() >= minRegionX && regionCoordinate.x() <= maxRegionX && regionCoordinate.z() >= minRegionZ && regionCoordinate.z() <= maxRegionZ; + }) + .toList(); + for (final Path region : regions) { + final ChunkCoordinate regionCoordinate = ChunkCoordinate.fromRegionFile(region.getFileName().toString()) + .orElseThrow(IllegalStateException::new); + final int regionX = regionCoordinate.x(); + final int regionZ = regionCoordinate.z(); + final RegionFile regionFile = new RegionFile(region.toFile()); + for (final ChunkCoordinate offset : Hilbert.chunkCoordinateOffsets()) { + final ChunkCoordinate chunkCoordinate = new ChunkCoordinate((regionX << 5) + offset.x(), (regionZ << 5) + offset.z()); + if (chunkCoordinate.x() < minChunkX || chunkCoordinate.x() > maxChunkX || chunkCoordinate.z() < minChunkZ || chunkCoordinate.z() > maxChunkZ) { + continue; + } + regionFile.getChunk(chunkCoordinate.x(), chunkCoordinate.z()).ifPresent(chunk -> { + final boolean generated = chunk.getData().getString("Status") + .map(StringTag::value) + .map(status -> "minecraft:full".equals(status) || "full".equals(status)) + .orElse(false); + if (generated) { + chunks.add(chunkCoordinate); + saveData.append(chunkCoordinate.x()).append(',').append(chunkCoordinate.z()).append('\n'); + total.incrementAndGet(); + } + }); + } + } + Files.writeString(savePath, saveData, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/common/src/main/java/org/popcraft/chunky/util/ChunkCoordinate.java b/common/src/main/java/org/popcraft/chunky/util/ChunkCoordinate.java index 68c793cb..4a6cefeb 100644 --- a/common/src/main/java/org/popcraft/chunky/util/ChunkCoordinate.java +++ b/common/src/main/java/org/popcraft/chunky/util/ChunkCoordinate.java @@ -1,8 +1,27 @@ package org.popcraft.chunky.util; import java.util.Objects; +import java.util.Optional; public record ChunkCoordinate(int x, int z) implements Comparable { + public static Optional fromRegionFile(final String regionFileName) { + if (!regionFileName.startsWith("r.")) { + return Optional.empty(); + } + final int extension = regionFileName.indexOf(".mca"); + if (extension < 2) { + return Optional.empty(); + } + final String regionCoordinates = regionFileName.substring(2, extension); + final int separator = regionCoordinates.indexOf('.'); + final Optional regionX = Input.tryInteger(regionCoordinates.substring(0, separator)); + final Optional regionZ = Input.tryInteger(regionCoordinates.substring(separator + 1)); + if (regionX.isPresent() && regionZ.isPresent()) { + return Optional.of(new ChunkCoordinate(regionX.get(), regionZ.get())); + } + return Optional.empty(); + } + @Override public int compareTo(final ChunkCoordinate o) { return this.x == o.x ? Integer.compare(this.z, o.z) : Integer.compare(this.x, o.x); diff --git a/common/src/main/java/org/popcraft/chunky/util/Hilbert.java b/common/src/main/java/org/popcraft/chunky/util/Hilbert.java index 7b089474..2853a472 100644 --- a/common/src/main/java/org/popcraft/chunky/util/Hilbert.java +++ b/common/src/main/java/org/popcraft/chunky/util/Hilbert.java @@ -1,5 +1,8 @@ package org.popcraft.chunky.util; +import java.util.Arrays; +import java.util.Iterator; + public final class Hilbert { private static final ChunkCoordinate[] regionChunkCoordinateOffsets = { new ChunkCoordinate(0, 0), new ChunkCoordinate(0, 1), new ChunkCoordinate(1, 1), @@ -352,4 +355,8 @@ private Hilbert() { public static ChunkCoordinate regionDistanceToChunkCoordinateOffset(final int distance) { return regionChunkCoordinateOffsets[distance]; } + + public static ChunkCoordinate[] chunkCoordinateOffsets() { + return regionChunkCoordinateOffsets; + } } diff --git a/common/src/main/resources/lang/en.json b/common/src/main/resources/lang/en.json index 8f41d896..cec6c91d 100644 --- a/common/src/main/resources/lang/en.json +++ b/common/src/main/resources/lang/en.json @@ -78,6 +78,7 @@ "pattern_spiral": "spiral", "pattern_csv": "csv", "pattern_region": "region", + "pattern_world": "world", "shape_circle": "circle", "shape_diamond": "diamond", "shape_ellipse": "ellipse",