How to: Create custom world generators

Discussion in 'Resources' started by MatorKaleen, Jun 4, 2012.

Not open for further replies.
1. Offline

MatorKaleen

World generation has always been a very important part of Minecraft. Without, Minecraft would never have evolved as it did.
For Bukkit developers it also offers new possibilities: from simple plot generators up to massive worlds (think of the Aether, even if works with Forge, but who cares ).

So, now I want to give you a little overview, how world generation in bukkit is actually done.

1. Prerequisites
2. What is a chunk?
3. Coding of the plugin class
4. Coding of the generator class
5. Use it with bukkit
6. Summary
Prerequisites

Of course you need to know, how to code with Java. And i wont explain, how to build a plugin.
Even some mathematic skills are an advantage, especially spatial awareness.

What is a chunk?

Chunks are small parts of the world, that a generated, if a players get close to it.
A Minecraft chunk has a size of 16x16x256. To save memory, the chunk itself is split into 16 16x16x16 parts. The idea behind this is, that chunks in which is no block (or air) will be set to NULL.

The complete chunk is represented by this array:
Code:JAVA
`byte[][] result = new byte[world.getMaxHeight() / 16][];`

Before you can set blocks, you have to initialize the chunk part in which the block is:
Code:JAVA
`result[partY] = new byte[4096];`

partY is the position of the part in the chunk from bottom to top.

As you can see, each part consists of 12 bits. The first 4 Bits are the y coordinate, the 4 in the middle the z coordinate and the last 4 the x coordinate. For example, we want to set a stone block at the position P(12|8|5):
To set the block, you just have to set the Material ID into the array:
Code:JAVA
`result[2140] = (byte)Material.STONE.getId();`

Coding of the plugin class

To use the generator in your plugin, you have to override a function in your main class:
Code:JAVA
`import org.bukkit.generator.ChunkGenerator;import org.bukkit.plugin.java.JavaPlugin; public class YourWorldGeneratorPlugin extends JavaPlugin{public ChunkGenerator getDefaultWorldGenerator(String worldName, String id){return new YourGenerator();}}`

Coding of the generator class

For today, we will code just a simple flat world generator.
Take a look into the docu: click!
You can see, to generate a very simple world, you need a class that extends
Code:JAVA
`org.bukkit.generator.ChunkGenerator`

and has the function
Code:JAVA
`byte[][] generateBlockSections(World world, Random random, int x, int z, BiomeGrid biomes)`

Your basic class structure will look like this:
Code:JAVA
`import java.util.Random; import org.bukkit.World;import org.bukkit.generator.ChunkGenerator; public class YourGenerator extends ChunkGenerator {public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid){byte[][] result = new byte[world.getMaxHeight() / 16][]; //world height / chunk part height (=16, look above)return result;}}`

With that basis, you can generate everthing (even thinks like the Mandelbulb, but thats another topic).

Now let us add some blocks. To make it easier, we add a simple method, to set a block in the chunk:
Code:JAVA
`void setBlock(byte[][] result, int x, int y, int z, byte blkid) {    // is this chunk part already initialized?    if (result[y >> 4] == null) {        // Initialize the chunk part        result[y >> 4] = new byte[4096];    }    // set the block (look above, how this is done)    result[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = blkid;}`

From here it's very easy:
First we generate a layer of bedrock at y=0:
Code:JAVA
`for(x = 0; x < 16; x++){for(z = 0; z < 16; z++){setBlock(result, x, 0, z, (byte)Material.BEDROCK.getId());}}`

Then you generate two layers of dirt:
Code:JAVA
`for(x = 0; x < 16; x++){for(y = 1; y <= 2; y++){for (z = 0; z < 16; z++){setBlock(result, x, y, z, (byte)Material.DIRT.getId());}}}`

And at last you create a layer of grass:
Code:JAVA
`for(x = 0; x < 16; x++){for(z = 0; z < 16; z++){setBlock(result, x, 3, z, (byte)Material.GRASS.getId());}}`

There is an advanced version of this code attached.

Use it with bukkit

Now we have a generator written. But there are a few things to do:

First you have to make an entry in your plugin.yml
Code:
`load: STARTUP`
If you don't add this line, bukkit will generate the world and THEN load your plugin, so the plugin has to be loaded on startup.
Now you're ready to export it as jar file and copy it into your plugin folder.

As last step open your bukkit.yml and add this lines of code:
Code:
```worlds:
YourWorldNameHere:
generator: YourWorldGeneratorPlugin```
You have only to modify your level name in server.properties and you're ready to start bukkit.

Summary

After reading this, you should be able, to create own worlds with enough imagination. But there is more possible. Just have a look at BlockPopulator and PerlinNoiseGenerator in the Bukkit documentation.
I have one tip for you: Never put very heavy load code for the first generateBlockSections() call. On the first server start its ok, but later it will after every restart cause a huge laaag, once a player enters a new area.

If you find any mistakes, please report them to me.

Mator

BTW: As of 27.07.13 im currently planing to make a second tutorial about the generation itself. I just want to code an example project.

#1
Konato_K, Xtreme727, Dzikoysk and 5 others like this.
2. Offline

EDawg878

I'm reposting a question from the PlotMe developer zachbora:
Do you think you could explain how to make a generator that supports block values?

#2
3. Offline

MatorKaleen

I'll try it. But first, I will finish this tutorial here (maybe today , i hadn't much time in the last few days) and then i will look at that problem.

#3
4. Offline

Forge_User_10514669

you can use the "ChunkMaker" class of the plugin "MultiWorld" to make those chunks whit cuboids and other stuff, whtout to code mutch by your own

5. Offline

sablednah

I cant see it!

#5
6. Offline

MatorKaleen

Yes, I just have to comment it, but at the moment, I have to concentrate at school (OMG Reallife! )

#6
7. Offline

pivotgamer84

Could you explain how to make biomes generate?

I want to be able to specify that a specific biome generates at the spawn, but everything else has an ocean biome?

An example of this would be a survival island generator, where the island is unique to every seed by using biomes instead of specific blocks and block locations.

#7
8. Offline

MatorKaleen

Example code uploaded. If you find errors, please report it to me

#8
9. Offline

heylookoverthere

I would also like to learn how to do this.

10. Offline

MatorKaleen

Hm... Thats an intersting question. There are two possibilities: You handle it with the populator (The easy way), or the complex way: You create a delayed task. But i have to research this. Atm I'm working at another project. When I finished this, I will maybe make a tutorial about this.

#10
11. Offline

sd5

MatorKaleen: Your code is too difficult. Instead of the byte[][] you should use a byte[] with size 65536 (16 * 16 * 256) called blocks and add a method:
Code:
```private int convert(int x, int y, int z) {
return (x * 16 + z) * 256 + y;
}```
To create a block simply do now:
Code:
`blocks[convert(x, y, z)] = (byte) Material.STONE.getId();`
Finally just return blocks.

#11
JWhy likes this.
12. Offline

MatorKaleen

sd5
The way, you describe, is
1. Deprecated (look here)
2. Much more memory intensive: a byte[65536] is larger than a byte[16][], because you needn't to initialise every 16 arrays in the second dimension (it's hard to explain in english for me, sry)

Maybe this is more difficult as your one, but I think, this is the more effecient way.

Mator

#12
13. Offline

ZachBora

I will change the code and see if it works. It's too bad that chunk gen still doesn't support block data.

#13
14. Offline

Aza24

Looks good! Just one tip, use the getMaxHeight(); method instead of specifying the height.

Code:Java
`public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {    byte[][] result = new byte[world.getMaxHeight() / 16][];    return result;}`

#14
15. Offline

MatorKaleen

[quote uid=90565375 name="Aza24" post=1327402]Looks good! Just one tip, use the getMaxHeight(); method instead of specifying the height.

Code:Java
`public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {byte[][] result = new byte[world.getMaxHeight() / 16][];return result;}`
[/quote]

Thanks for the tip, I will update the download the next few days

Here's a little new screenshot, what I'm doing atm: <Edit by Moderator: Redacted mediafire url>

Last edited by a moderator: Nov 10, 2016
#15
16. Offline

Aza24

[quote uid=90686358 name="MatorKaleen" post=1328161]Thanks for the tip, I will update the download the next few days

Here's a little new screenshot, what I'm doing atm: <Edit by Moderator: Redacted mediafire url>

Cool, do you know how to make light update on generated chunks because I generated glowstone and no light is given off until the block is updated.

Last edited by a moderator: Nov 10, 2016
#16
17. Offline

MatorKaleen

After a long time, I updated my tutorial a little bit. Just wanted to say

#17
18. Offline

#18
19. Offline

MatorKaleen

I never wanted to touch Minecraft again (for some reasons xD), but I just logged in and saw, somebody replied to my post. I'm downloading eclipse right now

#19
negative_codezZ and Skyost like this.
20. Offline

Monkeyboystein

Ok. Looking at this where would i put the:

for(x = 0; x < 16; x++)
{
for(y = 1; y <= 2; y++)
{
for (z = 0; z < 16; z++)
{
setBlock(result, x, y, z, (byte)Material.ICE.getId());
}
}
}

#20
21. Offline

JWhy

Monkeyboystein: You'd use setBlock(...) in your generateBlockSections(...) method:
Code:
```    public byte[][] generateBlockSections(World world, Random random,
int chunkX, int chunkZ, BiomeGrid biomeGrid) {
byte[][] result = new byte[world.getMaxHeight() / 16][];

for (int x = 0; x < 16; x++) {
for (int y = 1; y <= 2; y++) {
for (int z = 0; z < 16; z++) {
setBlock(result, x, y, z, (byte) Material.ICE.getId());
}
}
}

return result;
}```

#21