Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 Sukikui
Copyright (c) 2026 Sukikui

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
125 changes: 96 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@

# BiomeMap

BiomeMap generates a lightweight JSON file mapping each world region to its dominant biome. Ideal for creating stylized biome maps on external web apps.
Generates a lightweight JSON file mapping each world region to its dominant biome. Ideal for creating stylized biome maps on external web apps.

</div>

## 📋 Overview

BiomeMap scans a configurable grid (default ±5000 blocks around spawn, sampled in 32×32 cells) and writes the dominant
biome for each cell to `plugins/BiomeMap/data/biome-map.json`.
This JSON is meant for external dashboards or stylized maps—no world edits,
no database, just a clean file your frontend can colorize.
BiomeMap exports the dominant biome of a rectangular selection.

You choose a world and 2 corners (`x/z`), and the plugin builds a grid of cells over that area.
Then it samples biomes and writes:
- a JSON file
- optionally a PNG preview (`1 pixel = 1 cell`)

It is read-only: no world edits, no database, no extra services.

## ✨ Features

- Samples center + corners of every cell to smooth biome transitions
- Handles `/biomemap [world] [cellSize]` with tab completion
- Pretty-printed JSON with origin, dimensions, and biome ids (`minecraft:plains`, etc.)
- Logs progress to console and in-game so you can monitor long exports
- Read-only: the world is never modified
- Rectangular selection defined by two coordinates (`/biomemap world x1 z1 x2 z2 [cellSize] [preview]`)
- Cell size with chunk-friendly alignment (`8`, `16`, `32`, ...)
- Chunk-based sampling to smooth biome transitions
- Asynchronous processing with frequent progress updates (no server freeze)
- One export at a time (global lock)
- Stop command to cancel running exports cleanly
- Structured JSON output with min/max bounds per cell and namespaced biome IDs
- Optional PNG preview output (`1 pixel = 1 cell`) using a biome RGB palette

## 🚀 Installation

Expand All @@ -30,38 +37,98 @@ no database, just a clean file your frontend can colorize.

## 🕹 Command Usage

| Command | Arguments | Description |
|--------------------------------|----------------------------------------------------------|------------------------------------------------------------------|
| `/biomemap [world] [cellSize]` | `world` defaults to `world`, `cellSize` defaults to `32` | Generates/overwrites `data/biome-map.json` for the target world. |
| Command | Arguments | Description |
| --- | --- | --- |
| `/biomemap <world> <x1> <z1> <x2> <z2> [cellSize] [preview]` | `world` required, `cellSize` default `16`, `preview` optional | Exports the full rectangle between the 2 points. If `preview` is present, also writes a PNG. |
| `/biomemap stop` | none | Stops the current export. |

Notes:
- Good `cellSize` values: `8`, `16`, `32`, `64`, ...
- `preview` can be written as `preview` or `--preview`
- If an export is stopped, files for that run are deleted from `exports/`

Example:
```
/biomemap world -512 -512 320 192 32
```
→ covers the area between `(-512,-512)` and `(320,192)` using 32×32-block cells (chunk-aligned).

Typical console output:
Preview example:
```
[BiomeMap] Exporting biomes for world 'world' (cellSize=32)...
[BiomeMap] Export complete: 97,344 cells saved to biome-map.json
/biomemap world -512 -512 320 192 32 preview
```
→ same JSON export + PNG preview with one pixel per cell.

### 📁 Output files

Exports are written to `plugins/BiomeMap/exports/`.

JSON files use:
- `<world>_<cellSize>_<index>.json`

If the filename already exists, `index` is incremented (`world_32_1.json`, `world_32_2.json`, ...).

If `preview` is enabled, the plugin also writes PNG files with the same base name as each JSON file:
- `plugins/BiomeMap/exports/<world>_<cellSize>_<index>.png`

Biome colors come from `src/main/java/fr/sukikui/biomemap/export/BiomeColorPalette.java` (vanilla 1.21.11 palette with deterministic fallback for unknown biome ids).

### ⚙️ Configuration

`config.yml` exposes performance throttles.

| Key | Default | Description |
| --- | --- | --- |
| `performance.chunks-per-tick` | `1` | How many new chunk jobs are started each tick. Lower = safer, slower. |
| `performance.max-in-flight` | `4` | Max number of BiomeMap jobs currently in pipeline (queued/running). |
| `performance.max-concurrent-chunks` | `64` | Max real chunk loads at the same time (main server pressure knob). |

Quick tuning guide:
- If players feel lag, lower `max-concurrent-chunks` first.
- If export feels too slow but server is stable, increase `chunks-per-tick` a bit.
- Keep `max-in-flight >= chunks-per-tick`.

## 🗺 JSON Format

```json
{
"cellSize": 32,
"origin": { "x": -5000, "z": -5000 },
"width": 312,
"height": 312,
"cellSize": 16,
"selectionMin": { "x": -200, "z": -200 },
"selectionMax": { "x": -50, "z": -20 },
"gridOrigin": { "x": -208, "z": -208 },
"width": 10,
"height": 12,
"cells": [
{ "i": 0, "j": 0, "biome": "minecraft:plains" },
{ "i": 1, "j": 0, "biome": "minecraft:forest" }
{
"i": 0,
"j": 0,
"bounds": {
"min": { "x": -208, "z": -208 },
"max": { "x": -193, "z": -193 }
},
"biome": "minecraft:plains"
},
{
"i": 1,
"j": 0,
"bounds": {
"min": { "x": -192, "z": -208 },
"max": { "x": -177, "z": -193 }
},
"biome": "minecraft:forest"
}
]
}
```

| Field | Type | Description |
|-------------------------|----------|-----------------------------------------------------------|
| `cellSize` | `number` | Width/height of each grid cell in blocks. |
| `origin.x`,`origin.z` | `number` | Southwest corner of the scanned square (spawn − radius). |
| `width`,`height` | `number` | Number of cells sampled on each axis. |
| `cells[].i`,`cells[].j` | `number` | Grid indices; world coords = `origin + index * cellSize`. |
| `cells[].biome` | `string` | Namespaced biome id returned by Paper. |
| Field | Type | Description |
| --- | --- | --- |
| `cellSize` | `number` | Cell size in blocks (minimum 8; values above that are aligned to the chunk grid). |
| `selectionMin/Max` | `object` | Raw coordinates provided in the command. |
| `gridOrigin` | `object` | North-west corner of the grid (min X, min Z). |
| `width`, `height` | `number` | Number of cells on the X and Z axes. |
| `cells[].bounds.min/max` | `object` | Inclusive bounds delimiting the cell. |
| `cells[].biome` | `string` | Namespaced biome ID (e.g. `minecraft:savanna`). |

---

Expand Down
32 changes: 28 additions & 4 deletions src/main/java/fr/sukikui/biomemap/BiomeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,53 @@
*/
public final class BiomeMap extends JavaPlugin {

public static final int DEFAULT_CELL_SIZE = 32;
public static final int SCAN_RADIUS = 5000;
public static final int DEFAULT_CELL_SIZE = 16;

private BiomeMapCommand commandHandler;
private int chunksPerTick;
private int maxInFlight;
private int maxConcurrentChunks;

@Override
public void onEnable() {
PaperLib.suggestPaper(this);
ensureDataFolder();
saveDefaultConfig();
loadPerformanceSettings();

Logger logger = getLogger();
File dataFolder = getDataFolder();
BiomeExporter exporter = new BiomeExporter(dataFolder, SCAN_RADIUS);
BiomeExporter exporter = new BiomeExporter(dataFolder);

BiomeMapCommand commandHandler = new BiomeMapCommand(exporter, logger, DEFAULT_CELL_SIZE);
commandHandler =
new BiomeMapCommand(
this, exporter, logger, DEFAULT_CELL_SIZE, chunksPerTick, maxInFlight,
maxConcurrentChunks);
PluginCommand biomemapCommand =
Objects.requireNonNull(
getCommand("biomemap"), "Command biomemap not defined in plugin.yml");
biomemapCommand.setExecutor(commandHandler);
biomemapCommand.setTabCompleter(commandHandler);
}

@Override
public void onDisable() {
if (commandHandler != null) {
commandHandler.cancelAllExports();
}
}

private void ensureDataFolder() {
if (!getDataFolder().exists() && !getDataFolder().mkdirs()) {
getLogger().warning("Unable to create plugin data folder. Exports may fail.");
}
}

private void loadPerformanceSettings() {
chunksPerTick = Math.max(1, getConfig().getInt("performance.chunks-per-tick", 1));
int configuredMaxInFlight = getConfig().getInt("performance.max-in-flight", chunksPerTick * 2);
maxInFlight = Math.max(chunksPerTick, configuredMaxInFlight);
maxConcurrentChunks = Math.max(
1, getConfig().getInt("performance.max-concurrent-chunks", 64));
}
}
Loading
Loading