Developing a World Generator Plugin

## Introduction

This tutorial will give you the basic steps of creating a custom world generator using Bukkit. Following this tutorial, you will be making a simple, infinite world of plains and oceans, with some features added.

**IMPORTANT:**

* Before reading this tutorial, you are required to know the basics of the Java programming language, and the basics of creating a proper Bukkit plugin. If you have not yet, click here.
* This tutorial uses Eclipse as the IDE and Bukkit 1.12+. Other IDEs and Bukkit versions may have some differences.

## Set up the project

* Create a Maven project in Eclipse, add the Bukkit dependency to the pom.xml and manage it:

* Create plugin.yml in src/main/resources folder, type in your plugin's information. Finally **add**
```
load: startup

```
 **to the file**.

* Create the main class in your project's main package. Remember the path to that class must be **the same** as the *main* key in your *plugin.yml*. Override onEnable() and onDisable() in the class with your code.

The main class now should be similar to this:

```
package {$TopLevelDomain}.{$Domain}.{$PluginName};

import org.bukkit.plugin.java.JavaPlugin;

public final class {$PluginName} extends JavaPlugin {
 @Override
 public void onEnable() {
 // TODO Insert logic to be performed when the plugin is enabled
 }

 @Override
 public void onDisable() {
 // TODO Insert logic to be performed when the plugin is disabled
 }
}

```

## Create a custom chunk generator

A Minecraft's world is divided into small chunks. Each chunk is 16×256×16 blocks and is created by a *ChunkGenerator*. After that the world meets some *BlockPopulator* 's to be filled in with the details.

* First, create a new class in the main package extending the ChunkGenerator class. We will call this *CustomChunkGenerator*.
* Create a method
```
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome)

```
 with the *@Override* annotation. Now it should look like this:

```
public class CustomChunkGenerator extends ChunkGenerator {
 @Override
 public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome) {
 // TODO Chunk generation code here.
 }
}

```

This method will be called anytime a new chunk is being created to get the chunk's details.

* Add a *ChunkData* variable to the method and assign it with *createChunkData(world)*:

```
ChunkData chunk = createChunkData(world);

```

We will fill the chunk's blocks into this variable and return it to the method.

### Adding a height generator

Each biome in Minecraft has different heights, and in that biome the heights vary too. For example the Plains has a flat, low terrain, but the Extreme Hills biome has super high, steep, rocky cliffs. The height of each location is determined by an *octave generator*. It will make a randomized, but smooth terrain and you can use it for creating different kinds of biomes.

* Create a *SimplexOctaveGenerator* in the method, use the provided world's seed as its random's seed:

```
SimplexOctaveGenerator generator = new SimplexOctaveGenerator(new Random(world.getSeed()), 8);

```

The second argument is the number of octaves.
**Remember not to place the octave generator anywhere else.**

* Set the scale of *generator* to 0.005D.

```
generator.setScale(0.005D);

```

You can change the scale if you want. The larger the scale is, the steeper the terrain.

### Generate the chunk's heights and blocks

Each chunk's x×z dimension is always 16×16. We will use the *SimplexOctaveGenerator* above to determine the height of each (x, z) coordinate, and then generate blocks vertically onto that coordinate.

* Create a *for* loop for integer *z* inside the a *for* loop for interger *x*. They will run from (0, 0) to (15, 15) which indicates the current (x, z) coordinate we are working with:

```
for (int x = 0; x < 16; x++)
 for (int z = 0; z < 16; z++) {
 // TODO Heights and blocks generation code here.
 }

```

* Make an integer *currentHeight* inside the class. As the name explains, this variable stores the current height acquired from the *SimplexOctaveGenerator* each time we work with an (X,Z) coordinate of the chunk.

* In the *for* loop, assign the octave generator's noise of the current (X,Z) coordinate of the world to *currentHeight* after adding the result with 1, multiplying it by 15, adding it with 50 and casting it to integer.

The current (X,Z) coordinate of the world can be retrieved by multiplying the *chunkX*, *chunkZ*provided and adding each of them with the current chunks's X, Z:

```
currentHeight = (int) ((generator.noise(chunkX \* 16 + x, chunkZ \* 16 + z, 0.5D, 0.5D, true) +1 ) \* 15D + 50D);

```

Why 15 and 50? 15 - the multiplier is the amount of difference between the highest and lowest possible heights of the world, and 50 is the minimum height of the whole world. You can change these if you want.

* After retrieving the height, we are going to set the blocks for the current (X,Z) coordinate.

* Set the highest block of the "pillar" to grass block:

```
chunk.setBlock(x, currentHeight, z, Material.GRASS);

```

* Set the lower block to dirt:

```
chunk.setBlock(x, currentHeight-1, z, Material.DIRT);

```

* From the third block to the almost bottom block of the "pillar", place blocks of stone:

```
for (int i = currentHeight-2; i > 0; i--)
 chunk.setBlock(x, i, z, Material.STONE);

```

* Finally, set the very bottom block to bedrock:

```
chunk.setBlock(x, 0, z, Material.BEDROCK);

```

* At the last line of the method, return the *chunk*:

```
return chunk;

```

Now the *CustomChunkGenerator* should look like this:

```
public class CustomChunkGenerator extends ChunkGenerator {
 // Remember this
 int currentHeight = 50;

 @Override
 public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome) {
 SimplexOctaveGenerator generator = new SimplexOctaveGenerator(new Random(world.getSeed()), 8);
 ChunkData chunk = createChunkData(world);
 generator.setScale(0.005D);

 for (int X = 0; X < 16; X++)
 for (int Z = 0; Z < 16; Z++) {
 currentHeight = (int) (generator.noise(chunkX\*16+X, chunkZ\*16+Z, 0.5D, 0.5D)\*15D+50D);
 chunk.setBlock(X, currentHeight, Z, Material.GRASS\_BLOCK);
 chunk.setBlock(X, currentHeight-1, Z, Material.DIRT);
 for (int i = currentHeight-2; i > 0; i--)
 chunk.setBlock(X, i, Z, Material.STONE);
 chunk.setBlock(X, 0, Z, Material.BEDROCK);
 }
 return chunk;
 }
}

```

### Test the generator

* Add these lines to your main class:

```
@Override
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
 return new CustomChunkGenerator();
}

```

This will make our chunk generator in action when new chunks is needed.

* Build the plugin by Maven Build → *package* goal. It should have built the .jar successfully.

* Prepare a Bukkit server. Put the compiled plugin into the *plugins* folder. Delete the main world save folder. Add these lines to bukkit.yml:

```
 generator: plugin
# Replace "plugin" with your plugin's name.

```

* Start the server. Login to the server. You will see something like this:

It's a pretty flat, boring world, much like the beginning of Minecraft (the very first version of Minecraft was a flat cube of blocks), but don't worry, we are going to add more stuff into this world later.

## Creating block populators

As explained above, after a new chunk is filled in with the basic terrain, it will meet some *block populators* to be filled in with the details and structures, such as: trees, ores, dungeons, mineshafts, etc...

In this section, we are going to create some block populators to spawn some trees, tall grass, some water and lava lakes, and some veins of ore.

### Create a tree populator

This will make a 50-50 chance of creating 1 to 5 trees per chunk.

* First, create a class named *TreePopulator* extending the class *BlockPopulator*.

If the IDE warns you about making the class abstract, ignore it and continue on the way.

* Override the superclass's method *populate*:

```
@Override
public void populate(World world, Random random, Chunk chunk) {
 // TODO Tree populator's code here
}

```

Again, don't make the method abstract even if the IDE warns you. It will be OK.

* Use the provided *random* inside the method to make the chance of spawning trees:

```
if (random.nextBoolean()) {
 // TODO Tree generation code here
}

```

* Inside the *if*, make a *for* loop of an integer *i* running from 1 to a random integer from 1 to 5.

* Inside the *for* loop, generate a tree on a random location. Luckily, we don't have to build the tree block-by-block. Instead, we get the highest block on a randomly chosen (X,Z) coordinate of the chunk and generate a tree on top of the block by an available tree generation method from Bukkit:

```
if (random.nextBoolean()) {
 int amount = random.nextInt(4)+1; // Amount of trees
 for (int i = 1; i < amount; i++) {
 int X = random.nextInt(15);
 int Z = random.nextInt(15);
 int Y;
 for (Y = world.getMaxHeight()-1; chunk.getBlock(X, Y, Z).getType() == Material.AIR; Y--); // Find the highest block of the (X,Z) coordinate chosen.
 world.generateTree(chunk.getBlock(X, Y, Z).getLocation(), TreeType.TREE); // The tree type can be changed if you want.
 }
}

```

### Registering block populators

Like the chunk generator, every block populators must be registered before they can work.

Inside the *CustomChunkGenerator* class, insert this method:

```
@Override
public List<BlockPopulator> getDefaultPopulators(World world) {
 return Arrays.asList((BlockPopulator)new TreePopulator());
}

```

Now you can recompile the plugin and see it works in the server. It will be something like this:

### Generating grass

* Create a new class similar to the *TreePopulator*, but now we name it *GrassPopulator*.

This generation is easy enough for you to do yourself. Here is the hints:

* Choose a random (X,Z) coordinate.

* Find the highest block of that coordinate.

* On the top of that block, place grass by this method:

```
chunk.getBlock(X, Y+1, Z).setType(Material.GRASS);

```

### Generating Small Lakes

In the next steps we are going to make a chance of spawning a water or lava lake for each chunk.

Create a new *BlockPopulator* class, name it something like *LakePopulator*.

This time we don't use a boolean generator. Instead we randomize an integer from 1 to 100 and check if it is smaller than a cetain number. If true it will determine if it is a water, or lava lake. Then the lake's location is determined, finally we begin the generation.

Not as lucky as the trees, this time we have to build our lake ourselves.

Below is the code for the generation of lakes. It has been converted from the vannila Minecraft's lake generator.

```
 if (random.nextInt(100) < 10) {
 int chunkX = source.getX();
 int chunkZ = source.getZ();

 int randomX = chunkX \* 16 + random.nextInt(16);
 int randomZ = chunkZ \* 16 + random.nextInt(16);
 int y = 0;

 for (y = world.getMaxHeight() - 1; world.getBlockAt(randomX, y, randomZ).getType() == Material.AIR; y--) ;
 y -= 7;

 Block block = world.getBlockAt(randomX + 8, y, randomZ + 8);

 if (random.nextInt(100) < 90) {
 block.setType(Material.WATER);
 } else {
 block.setType(Material.LAVA);
 }

 boolean[] booleans = new boolean[2048];

 int i = random.nextInt(4) + 4;

 int j, j1, k1;

 for (j = 0; j < i; ++j) {
 double d0 = random.nextDouble() \* 6.0D + 3.0D;
 double d1 = random.nextDouble() \* 4.0D + 2.0D;
 double d2 = random.nextDouble() \* 6.0D + 3.0D;
 double d3 = random.nextDouble() \* (16.0D - d0 - 2.0D) + 1.0D + d0 / 2.0D;
 double d4 = random.nextDouble() \* (8.0D - d1 - 4.0D) + 2.0D + d1 / 2.0D;
 double d5 = random.nextDouble() \* (16.0D - d2 - 2.0D) + 1.0D + d2 / 2.0D;

 for (int k = 1; k < 15; ++k) {
 for (int l = 1; l < 15; ++l) {
 for (int i1 = 0; i1 < 7; ++i1) {
 double d6 = (k - d3) / (d0 / 2.0D);
 double d7 = (i1 - d4) / (d1 / 2.0D);
 double d8 = (l - d5) / (d2 / 2.0D);
 double d9 = d6 \* d6 + d7 \* d7 + d8 \* d8;

 if (d9 < 1.0D) {
 booleans[(k \* 16 + l) \* 8 + i1] = true;
 }
 }
 }
 }
 }

 for (j = 0; j < 16; ++j) {
 for (k1 = 0; k1 < 16; ++k1) {
 for (j1 = 0; j1 < 8; ++j1) {
 if (booleans[(j \* 16 + k1) \* 8 + j1]) {
 world.getBlockAt(randomX + j, y + j1, randomZ + k1).setType(j1 > 4 ? Material.AIR : block.getType());
 }
 }
 }
 }

 for (j = 0; j < 16; ++j) {
 for (k1 = 0; k1 < 16; ++k1) {
 for (j1 = 4; j1 < 8; ++j1) {
 if (booleans[(j \* 16 + k1) \* 8 + j1]) {
 int X1 = randomX + j;
 int Y1 = y + j1 - 1;
 int Z1 = randomZ + k1;

 if (world.getBlockAt(X1, Y1, Z1).getType() == Material.DIRT) {
 world.getBlockAt(X1, Y1, Z1).setType(Material.GRASS);
 }
 }
 }
 }
 }
 }

```

### Generating veins of ore

Below is the code for generating some coal veins

```
int X, Y, Z;
boolean isStone;
for (int i = 1; i < 15; i++) { // Number of tries
 if (random.nextInt(100) < 60) { // The chance of spawning
 X = random.nextInt(15);
 Z = random.nextInt(15);
 Y = random.nextInt(40)+20; // Get randomized coordinates
 if (world.getBlockAt(X, Y, Z).getType() == Material.STONE) {
 isStone = true;
 while (isStone) {
 world.getBlockAt(X, Y, Z).setType(Material.COAL\_ORE);
 if (random.nextInt(100) < 40) { // The chance of continuing the vein
 switch (random.nextInt(6)) { // The direction chooser
 case 0: X++; break;
 case 1: Y++; break;
 case 2: Z++; break;
 case 3: X--; break;
 case 4: Y--; break;
 case 5: Z--; break;
 }
 isStone = (world.getBlockAt(X, Y, Z).getType() == Material.STONE) && (world.getBlockAt(X, Y, Z).getType() != Material.COAL\_ORE);
 } else isStone = false;
 }
 }
 }
}

```

You can use this for creating other types of vein.