From ffdf386d6b6518ed1d7b63389b8c1e5fd63ca3a6 Mon Sep 17 00:00:00 2001 From: Sukikui Date: Thu, 28 May 2026 17:30:07 +0200 Subject: [PATCH] feat: lower minimum cell size to 4 --- build.gradle | 20 ++++---- .../biomemap/command/BiomeMapCommand.java | 31 ++++++------ .../biomemap/export/AsyncBiomeExportTask.java | 16 +++---- .../fr/sukikui/biomemap/util/GridMath.java | 47 +++++++++++++++++++ .../sukikui/biomemap/util/GridMathTest.java | 33 +++++++++++++ 5 files changed, 113 insertions(+), 34 deletions(-) create mode 100644 src/main/java/fr/sukikui/biomemap/util/GridMath.java create mode 100644 src/test/java/fr/sukikui/biomemap/util/GridMathTest.java diff --git a/build.gradle b/build.gradle index 2c73723..e7bea08 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = "fr.sukikui.biomemap" -version = "0.1.4" +version = "0.1.5" def minecraftVersion = '26.1.2' def paperApiVersion = "${minecraftVersion}.build.65-stable" @@ -62,15 +62,17 @@ test { } processResources { + def resourceProperties = [ + NAME: rootProject.name, + VERSION: project.version.toString(), + PACKAGE: rootProject.group.toString(), + AUTHOR: "Sukikui", + DESCRIPTION: "Lightweight PaperMC plugin exporting dominant biomes from a selected area to JSON", + API_VERSION: minecraftVersion + ] + inputs.properties(resourceProperties) filesMatching("**/plugin.yml") { - expand( - NAME: rootProject.name, - VERSION: version, - PACKAGE: rootProject.group.toString(), - AUTHOR: "Sukikui", - DESCRIPTION: "Lightweight PaperMC plugin exporting dominant biomes from a selected area to JSON", - API_VERSION: minecraftVersion - ) + expand(resourceProperties) } } diff --git a/src/main/java/fr/sukikui/biomemap/command/BiomeMapCommand.java b/src/main/java/fr/sukikui/biomemap/command/BiomeMapCommand.java index d863099..f981d09 100644 --- a/src/main/java/fr/sukikui/biomemap/command/BiomeMapCommand.java +++ b/src/main/java/fr/sukikui/biomemap/command/BiomeMapCommand.java @@ -3,6 +3,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import fr.sukikui.biomemap.export.AsyncBiomeExportTask; import fr.sukikui.biomemap.export.BiomeExporter; +import fr.sukikui.biomemap.util.GridMath; import fr.sukikui.biomemap.util.ProgressFormatter; import java.io.File; import java.nio.file.Path; @@ -31,8 +32,7 @@ @SuppressFBWarnings("EI_EXPOSE_REP2") public final class BiomeMapCommand implements CommandExecutor, TabCompleter { - private static final int CHUNK_SIZE = 16; - private static final int MIN_CELL_SIZE = 8; + private static final int MIN_CELL_SIZE = 4; private static final String CHAT_PREFIX = "§8[§b§lBiomeMap§8] §r"; private static final int PLAYER_STATUS_MAP_MAX_WIDTH = 28; private static final int PLAYER_STATUS_MAP_MAX_HEIGHT = 10; @@ -173,14 +173,11 @@ public boolean onCommand(CommandSender sender, Command command, String label, St long totalCells = (long) width * height; long totalChunks; - if (cellSize >= CHUNK_SIZE) { - int chunksPerCell = Math.max(1, cellSize / CHUNK_SIZE); - totalChunks = totalCells * (long) chunksPerCell * chunksPerCell; - } else { - int cellsPerChunk = CHUNK_SIZE / cellSize; - int chunkColumns = Math.max(1, (width + cellsPerChunk - 1) / cellsPerChunk); - int chunkRows = Math.max(1, (height + cellsPerChunk - 1) / cellsPerChunk); - totalChunks = (long) chunkColumns * chunkRows; + try { + totalChunks = GridMath.countChunksForGrid(originX, originZ, width, height, cellSize); + } catch (IllegalArgumentException ex) { + sendError(sender, "Selection is too large to export."); + return true; } notifyInfo( sender, @@ -268,7 +265,7 @@ public List onTabComplete( } return Collections.emptyList(); } else if (args.length >= 6) { - return List.of("8", "16", "32", "64", "128", "256", "preview"); + return List.of("4", "8", "16", "32", "64", "128", "256", "preview"); } return Collections.emptyList(); } @@ -410,14 +407,18 @@ private int alignCellSize(int requestedSize) { if (requestedSize <= MIN_CELL_SIZE) { return MIN_CELL_SIZE; } - if (requestedSize < CHUNK_SIZE) { - return CHUNK_SIZE; + if (requestedSize < GridMath.CHUNK_SIZE) { + int alignedSize = MIN_CELL_SIZE; + while (alignedSize < GridMath.CHUNK_SIZE && requestedSize > alignedSize) { + alignedSize *= 2; + } + return alignedSize; } - int remainder = requestedSize % CHUNK_SIZE; + int remainder = requestedSize % GridMath.CHUNK_SIZE; if (remainder == 0) { return requestedSize; } - return requestedSize + (CHUNK_SIZE - remainder); + return requestedSize + (GridMath.CHUNK_SIZE - remainder); } private void notifyInfo(CommandSender sender, String senderMessage, String logMessage) { diff --git a/src/main/java/fr/sukikui/biomemap/export/AsyncBiomeExportTask.java b/src/main/java/fr/sukikui/biomemap/export/AsyncBiomeExportTask.java index 6f8a3d8..e5a584b 100644 --- a/src/main/java/fr/sukikui/biomemap/export/AsyncBiomeExportTask.java +++ b/src/main/java/fr/sukikui/biomemap/export/AsyncBiomeExportTask.java @@ -4,6 +4,7 @@ import fr.sukikui.biomemap.export.BiomeExporter.BiomeCell; import fr.sukikui.biomemap.export.BiomeExporter.BiomeMapExport; import fr.sukikui.biomemap.export.BiomeExporter.Point; +import fr.sukikui.biomemap.util.GridMath; import fr.sukikui.biomemap.util.ProgressFormatter; import java.io.File; import java.io.IOException; @@ -42,7 +43,7 @@ @SuppressFBWarnings("EI_EXPOSE_REP2") public final class AsyncBiomeExportTask extends BukkitRunnable { - private static final int CHUNK_SIZE = 16; + private static final int CHUNK_SIZE = GridMath.CHUNK_SIZE; private static final String CHAT_PREFIX = "§8[§b§lBiomeMap§8] §r"; private static final long FIRST_PROGRESS_HEARTBEAT_MS = TimeUnit.SECONDS.toMillis(10); private static final long PROGRESS_HEARTBEAT_MS = TimeUnit.MINUTES.toMillis(5); @@ -149,15 +150,10 @@ public AsyncBiomeExportTask( this.subChunkSampling = cellSize < CHUNK_SIZE; this.chunksPerCell = subChunkSampling ? 1 : Math.max(1, cellSize / CHUNK_SIZE); this.cellsPerChunk = subChunkSampling ? Math.max(1, CHUNK_SIZE / cellSize) : 1; - if (subChunkSampling) { - this.chunkColumns = Math.max(1, (width + cellsPerChunk - 1) / cellsPerChunk); - this.chunkRows = Math.max(1, (height + cellsPerChunk - 1) / cellsPerChunk); - } else { - this.chunkColumns = width * chunksPerCell; - this.chunkRows = height * chunksPerCell; - } - this.chunkStartX = Math.floorDiv(originX, CHUNK_SIZE); - this.chunkStartZ = Math.floorDiv(originZ, CHUNK_SIZE); + this.chunkColumns = GridMath.countChunksForCells(originX, width, cellSize); + this.chunkRows = GridMath.countChunksForCells(originZ, height, cellSize); + this.chunkStartX = GridMath.chunkStart(originX); + this.chunkStartZ = GridMath.chunkStart(originZ); this.chunkBiomes = new String[chunkColumns * chunkRows]; this.totalChunks = chunkBiomes.length; this.chunkCompletedMap = new boolean[chunkBiomes.length]; diff --git a/src/main/java/fr/sukikui/biomemap/util/GridMath.java b/src/main/java/fr/sukikui/biomemap/util/GridMath.java new file mode 100644 index 0000000..20b2a2d --- /dev/null +++ b/src/main/java/fr/sukikui/biomemap/util/GridMath.java @@ -0,0 +1,47 @@ +package fr.sukikui.biomemap.util; + +/** + * Shared grid/chunk calculations for biome exports. + */ +public final class GridMath { + + public static final int CHUNK_SIZE = 16; + + private GridMath() { + } + + /** + * Counts how many chunks are touched by a cell axis. + */ + public static int countChunksForCells(int origin, int cellCount, int cellSize) { + if (cellCount <= 0 || cellSize <= 0) { + throw new IllegalArgumentException("Cell count and size must be positive"); + } + + long minChunk = Math.floorDiv((long) origin, CHUNK_SIZE); + long maxBlock = (long) origin + ((long) cellCount * cellSize) - 1L; + long maxChunk = Math.floorDiv(maxBlock, CHUNK_SIZE); + long span = maxChunk - minChunk + 1L; + if (span > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Selection spans too many chunks"); + } + return Math.max(1, (int) span); + } + + /** + * Counts how many chunks are touched by a cell grid. + */ + public static long countChunksForGrid( + int originX, int originZ, int width, int height, int cellSize) { + long columns = countChunksForCells(originX, width, cellSize); + long rows = countChunksForCells(originZ, height, cellSize); + return columns * rows; + } + + /** + * Returns the chunk coordinate containing the provided block coordinate. + */ + public static int chunkStart(int origin) { + return Math.floorDiv(origin, CHUNK_SIZE); + } +} diff --git a/src/test/java/fr/sukikui/biomemap/util/GridMathTest.java b/src/test/java/fr/sukikui/biomemap/util/GridMathTest.java new file mode 100644 index 0000000..dbe5a0e --- /dev/null +++ b/src/test/java/fr/sukikui/biomemap/util/GridMathTest.java @@ -0,0 +1,33 @@ +package fr.sukikui.biomemap.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class GridMathTest { + + @Test + void countsAlignedSubChunkCellsInsideOneChunk() { + assertEquals(1, GridMath.countChunksForCells(0, 4, 4)); + assertEquals(1, GridMath.countChunksForCells(0, 2, 8)); + } + + @Test + void countsOffsetSubChunkCellsCrossingChunkBoundary() { + assertEquals(2, GridMath.countChunksForCells(8, 2, 8)); + assertEquals(2, GridMath.countChunksForCells(12, 3, 4)); + } + + @Test + void countsLargeCellsAcrossMultipleChunks() { + assertEquals(4, GridMath.countChunksForCells(0, 2, 32)); + assertEquals(6, GridMath.countChunksForCells(-32, 3, 32)); + } + + @Test + void rejectsInvalidCellGrid() { + assertThrows(IllegalArgumentException.class, () -> GridMath.countChunksForCells(0, 0, 4)); + assertThrows(IllegalArgumentException.class, () -> GridMath.countChunksForCells(0, 1, 0)); + } +}