World Generators
How to define, register, and build custom world generators on PowerNukkitX.
PowerNukkitX allows you to register custom world generators to build unique terrain systems (such as flat plots, skyblock hubs, custom dimensions, or structures).
1. Defining a Custom Generator
To create a custom world generator, extend org.powernukkitx.level.generator.Generator.
You must implement the constructor passing dimension and configuration options, and define the generation pipeline stages in stages(GenerateStage.Builder):
package org.example.generator;
import org.powernukkitx.level.DimensionData;
import org.powernukkitx.level.DimensionEnum;
import org.powernukkitx.level.generator.GenerateStage;
import org.powernukkitx.level.generator.Generator;
import org.powernukkitx.level.generator.stages.FinishedStage;
import org.powernukkitx.level.generator.stages.LightPopulationStage;
import org.powernukkitx.registry.Registries;
import java.util.Map;
public class MyFlatGenerator extends Generator {
public MyFlatGenerator(DimensionData dimensionData, Map<String, Object> options) {
// Enforce dimension data (e.g. Overworld) and configurations options
super(DimensionEnum.OVERWORLD.getDimensionData(), options);
}
@Override
public void stages(GenerateStage.Builder builder) {
// Configure the stage pipeline (e.g. initial generation stage, lighting, finalization)
builder.start(Registries.GENERATE_STAGE.get("MyCustomStage"))
.next(Registries.GENERATE_STAGE.get(LightPopulationStage.NAME))
.next(Registries.GENERATE_STAGE.get(FinishedStage.NAME));
}
@Override
public DimensionData getDimensionData() {
return DimensionEnum.OVERWORLD.getDimensionData();
}
}2. Registering the Generator
Register your custom generator inside your plugin's onLoad() phase:
import org.powernukkitx.registry.RegisterException;
import org.powernukkitx.registry.Registries;
@Override
public void onLoad() {
try {
Registries.GENERATOR.register("my_flat_generator", MyFlatGenerator.class);
} catch (RegisterException e) {
throw new RuntimeException(e);
}
}3. Populating Custom Structures in World Generation
To populate custom structures (e.g. houses, ruins, or floating balloons) during world generation, create a custom chunk Populator class and hook it into a custom PopulatorStage.
Step A: Define the Populator
Extend org.powernukkitx.level.generator.populator.Populator and override apply(ChunkGenerateContext context):
package org.example.generator;
import org.powernukkitx.level.Level;
import org.powernukkitx.level.Position;
import org.powernukkitx.level.format.IChunk;
import org.powernukkitx.level.generator.ChunkGenerateContext;
import org.powernukkitx.level.generator.object.BlockManager;
import org.powernukkitx.level.generator.populator.Populator;
import org.powernukkitx.level.structure.Structure;
import org.powernukkitx.level.structure.StructureAPI;
import org.powernukkitx.utils.random.RandomSourceProvider;
public class BalloonPopulator extends Populator {
public static final String NAME = "example:balloon";
private Structure balloonStructure;
@Override
public void apply(ChunkGenerateContext context) {
IChunk chunk = context.getChunk();
int chunkX = chunk.getX();
int chunkZ = chunk.getZ();
Level level = chunk.getLevel();
// Seed the randomizer for this chunk
this.random.setSeed(level.getSeed() ^ Level.chunkHash(chunkX, chunkZ) | chunkZ);
// Custom generation probability boundary checks
if (this.random.nextBoundedInt(100) >= 5) return; // 5% chance per chunk
int x = (chunkX << 4) + 7;
int z = (chunkZ << 4) + 7;
int highest = chunk.getLevel().getHighestBlockAt(x, z);
int y = highest + 30; // Spawn 30 blocks above the ground
if (balloonStructure == null) {
balloonStructure = StructureAPI.load("balloon_white");
}
if (balloonStructure != null) {
BlockManager manager = new BlockManager(level);
// Prepare structural coordinates and place
balloonStructure.preparePlace(new Position(x, y, z, level), manager);
manager.generateChunks();
queueObject(chunk, manager);
}
}
@Override
public String name() {
return NAME;
}
}Step B: Define the Generation Stage
Extend org.powernukkitx.level.generator.stages.PopulatorStage and return your populator's registered name:
package org.example.generator;
import org.powernukkitx.level.generator.stages.PopulatorStage;
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
public class BalloonPopulatorStage extends PopulatorStage {
public static final String NAME = "example:balloon_stage";
@Override
public ObjectArraySet<String> populators() {
// Return a set containing the registered names of populators to run in this stage
return new ObjectArraySet<>(new String[]{ BalloonPopulator.NAME });
}
@Override
public String name() {
return NAME;
}
}Step C: Register Populator & Stage
Register both classes inside your plugin's onLoad() phase:
import org.powernukkitx.registry.RegisterException;
import org.powernukkitx.registry.Registries;
@Override
public void onLoad() {
try {
// 1. Register the generator itself
Registries.GENERATOR.register("my_flat_generator", MyFlatGenerator.class);
// 2. Register the custom populator
Registries.POPULATOR.register(BalloonPopulator.NAME, BalloonPopulator.class);
// 3. Register the custom populator stage
Registries.GENERATE_STAGE.register(BalloonPopulatorStage.NAME, BalloonPopulatorStage.class);
} catch (RegisterException e) {
throw new RuntimeException(e);
}
}Step D: Register the Stage in your Generator Pipeline
Reference the custom stage key inside your generator's stage pipeline builder:
@Override
public void stages(GenerateStage.Builder builder) {
builder.start(Registries.GENERATE_STAGE.get(NormalTerrainStage.NAME))
.next(Registries.GENERATE_STAGE.get(BalloonPopulatorStage.NAME)) // Add stage
.next(Registries.GENERATE_STAGE.get(FinishedStage.NAME));
}3.5. Injecting into Normal (Vanilla) World Generation
If you want to inject custom structures (populators) into the default Minecraft Overworld generator without creating a custom generator or setting up new stages, you can append your populator's registered name directly to the static NormalPopulatorStage.POPULATORS list inside your plugin's initialization phase:
import org.powernukkitx.level.generator.stages.normal.NormalPopulatorStage;
import org.powernukkitx.registry.RegisterException;
import org.powernukkitx.registry.Registries;
@Override
public void onLoad() {
try {
// 1. Register the custom populator in the registry
Registries.POPULATOR.register(BalloonPopulator.NAME, BalloonPopulator.class);
// 2. Inject it directly into the vanilla Overworld populator stage list
NormalPopulatorStage.POPULATORS.add(BalloonPopulator.NAME);
} catch (RegisterException e) {
throw new RuntimeException(e);
}
}This ensures your custom structures generate naturally within default Overworld chunks alongside villages, dungeons, and temples.
4. Real-World Implementations
For production-ready references, examine these custom world generators:
- FuturePlots Generator: Uses a custom plot generation stage (
PlotStage) to carve out roads, walls, and plot boundaries, handling chunk regeneration async.