Minecraft Modding: Dimensions, WorldGen, Biomes, Trees

Definitions: Dimension, World, Chunk, Biome

Each of the "Overworld", the "Nether", and the "End" are called "dimensions".

Key Point: In terms of modding, each dimension is run in a separate instance of the World class. This is important as it means that you may have to think carefully if you need certain data across all the dimensions.

Due to the huge size of a world (a 256 by 256 area of the world has 17 million block positions!), the code manages data about the world in "chunks", saving them and loading them as needed. This is the reason for Minecraft's recognizable way that the world in the distance is often missing parts until you get closer. A chunk is 16 x 256 x 16 in size. "Chunk coordinates" are used in various places in the code and refer to x, y, z within the chunk.

Tip: To get chunk coordinates from world coordinates, you can use the modulo operator: chunkX = worldX%16. If you're not familiar with the % (modulo operator) see this tutorial.

When a chunk is loaded for the first time it is generated. This is done with a "chunk provider" that is called from the "world provider". There is a certain degree of randomness used by the code to make things interesting, but there are still "themes" for the area of the world which are called "biomes". These are things like "taiga" and "plains". The biome is used to determine the types of blocks, types of trees, and types of entities to generate.

Biomes consist of a number of chunks, but the shape and size can vary randomly.

Understanding World "Seeds" And "Noise" Generation

In computer programming, random numbers are not truly random but instead they are "pseudo-random". This means that while they seem random enough because they are not possible for a human to predict, but they may be possible for a computer to predict as long as the computer knows the starting point and generation algorithm of the random generation. This starting point is called the "seed".

The fact that the random number can be predicted with the seed is very useful for testing computer code because it means you can repeat the logic in the case where you are investigating a bug in code that

TipMinecraft allows the user to know the seed used in the world generation, by using the /seed command. You can then enter that seed when generating a new world, and the result will be the same. Users can share seeds to let other users experience worlds that they feel are worth sharing. For example, see this article on some cool shared seed values.
 uses randomness.

The world generation in Minecraft of course is random but it is also important that the generation looks "natural" (not totally random) and therefore uses the seed value to generate "noise". The term "noise" is used for randomness that causes variation around a certain values. For example, when randomly generating the world the height of the surface of the world isn't fully random but rather varies within a range (depending on the biome type) from the height of the neighboring blocks.

Key Point: Minecraft has a NoiseGenerator class which is extended into some specific noise generator types such as Perlin and simplex. These are used to determine whether to place a block in the world while giving a "natural" randomness. They typically work by creating a value that represents the "density" of the block position and if a certain threshold is met then a block is placed. This a a complicated subject but studying how they are used in vanilla Minecraft code should give you some ideas.

Key Point: The seed used for world generation is contained in the WorldSettings class (and saved/loaded with the WorldInfo class). However, on the client side the seed value is always 0 as it is never used on that side.

Explanation Of The Various Classes Related To Generation For World, Chunk, And Biomes

Thanks to Matryoshika and Choonster for this information.

There are a LOT of classes involved in generating a dimension. A world is comprised of chunks which are made up of blocks. Blocks are grouped together randomly to create "terrain" (i.e. the shape of the land in terms of mountains, ravines, mesas and such), which then have ores and caves populated within it, then have the surface converted to blocks consistent with the biome, which is then decorated with features such as trees and populated with structures like villages. There are multiple classes involved in each of these steps.

Because there are so many classes involved it can be confusing, and furthermore there can be several ways to accomplish similar results. This can cause additional confusion because different tutorials may give different steps; however, the reality is many of these classes are invoked during generation and so can place blocks according to a mods needs -- but it is still best to try to do it in the intended class.

Also, the links between the classes are somewhat convoluted. For example, the WorldProvider class has a WorldType as well as a BiomeProvider and ChunkProvider instances, but WorldType generates those instances.

Here is a brief overview of the various classes related to generation.
  • DimensionManager: Each dimension is defined by a DimensionType registered to the DimensionManager.
  • DimensionType: An enum that defines the dimension by associating an id and name with a WorldProvider instance. You don't create a DimensionType rather you register the information.
  • WorldProvider: The WorldProvider associates an instance of BiomeProvider, WorldType (instance field is called terrainType) and ChunkProvider. Also has methods to help assess aspects of the world like getBiomeForCoords().
  • BiomeProvider: (Prior to 1.9 this was called WorldChunkManager.) The BiomeProvider, in simplified terms, sets the selected chunk to a certain Biome. Yes, biomes are chunk-based, but it makes use of a byte[256] (16*16 block count) to set the blocks individually to a biome with desired size of width*depth.
  • ChunkProvider: The ChunkProvider is where the block are actually placed. It makes use of NoiseGeneratorOctaves/Perlin. It gets the BiomeProvider's biomesForGeneration, and sets them unto each block using the mentioned byte[]. It gets the Sealevel of the world, and then it starts to place blocks. Do note, on the size of worldgen as is done here, it does not use world.setBlockState(), rather it actually just makes use of pure numbers, representing the BlockPos, and setting to a blockstate, which has an enormous resource-cost decrease, comparably. The shape and form of the basic terrain is made by the mentioned perlin octave noise. The ChunkProvider also has a populate() method which fires the DecorateBiomeEvent. This is where IWorldGeneratorare fired, once the event is called. It also calls the BiomeDecorator.decorate() method which places dirt, gravel, clay, the ores etc in the world.
  • WorldType: Each world type is listed when you start the game (in the drop down). This is where the BiomeProvider and ChunkProvider instances originate from. The WorldType changes how the previously mentioned classes act to significantly edit the overall world generation process.: Vanilla Minecraft has the following selections:
    • DEFAULT: This is the standard type of Minecraft world.
    • FLAT: "Superflat", the terrain consists of one layer of bedrock, two layers of dirt, and one layer of grass. The entire world is plains biome type. The surface of the world is completely flat and at height y=4, with the exception of villages and other structures if they are enabled. Mobs will spawn as normal.
    • LARGE_BIOMES: Large biome worlds are generated mostly the same as default worlds, but scaled by 4 along the X and Z axis, causing the biomes of the Overworld to be expansive. Using the same seed for a default and large world will have the same relative geography, although rivers are not scaled.
    • AMPLIFIED: In this mode, landforms, biomes, and the size of all terrain in general is blown out of proportion. Mountains here can have summits up to Y level 256. The ground level is usually at least Y level 110. Oceans are much bigger, and landforms are extremely out of proportion.
    • CUSTOMIZED: A customizable world type, with four pages of settings. The first page contains customization options which affect how structures and biomes will generate. The second page contains settings for ore generation. The third and fourth page contain advanced terrain customization options. In addition to these options, there are several pre-made presets.
    • DEBUG: A world type containing all of the block states laid out in a grid at layer y=70 for convenient viewing. It can be accessed by holding down the ⇧ Shift key while cycling through the world types. In this world type there is a floor on Y = 60 made of barriers, and you cannot place or destroy blocks.
  • terrainType: The terrainType field is in the WorldProvider and references an instance of the WorldType being used for the active dimension.
  • Biome: A biome represents the top 5 or so blocks in the world to cover the surface and add "features" such as trees, as well the entities that can spawn in it. You can @Override the Biome#genTerrainBlocks() method to control how your custom biome's generated. Most vanilla biomes simply call generation of the default terrain, but BiomeMesa overrides it to generate completely custom terrain. Ore generation is done in Biome#decorate() and BiomeDecorator#decorate() methods. You can either override Biome#decorate() to add a few extra decorations or extend BiomeDecorator for completely custom decorations. 
  • BiomeProperties: An instance of this is associated with each Biome instance and contains information such as a name, temperature, height variation, whether rain is enabled, water color, etc.
  • BiomeDecorator: To use a custom BiomeDecorator, override Biome#createBiomeDecorator() method to call the Biome#getModdedBiomeDecorator() method with your custom BiomeDecorator and return the value returned by it.
  • IWorldGenerator: Not to be confused with the vanilla Minecraft WorldGenerator class, Forge provides the IWorldGenerator interface for which contains a generate() method which is called from the GameRegistry.generateWorld() method.
  • IChunkGenerator: This is a seemingly simple interface which is used in very complex ways. It has generateChunk() method as well as populate() and generateStructures() among others. There are vanilla implementations such as ChunkGeneratorOverworld, ChunkGeneratorEnd, ChunkGeneratorHell. These class implementations usually include a WorldType, NoiseGenerator extensions (for randomness), MapGenBase extensions, etc.
  • MapGenBase: Map generators are responsible for adding modular, somewhat regular structures like strongholds and villages.

Key Point: So basically if you want a custom world you should register a custom DimensionType which you then associate with a dimension id that you register to the DimensionManager. and is associated with a WorldProvider which has a WorldType instance that gives your custom ChunkProvider and BiomeProvider. Within the ChunkProvider you basically fire all the other stuff which can include custom BiomeDecorator and IWorldGenerator classes.

Example: Matryoshika has a GitHub project for 1.10.2, called Underworld, a Cave-World dimension that replaces the Overworld dimension, which makes use of all of these classes, and implements custom IWorldGenerators, though none for actual ores. See: https://github.com/Matryoshika/Underworld.

Getting A List Of All Biomes

Prior to 1.10.2 the built-in biomeList contains null entries and so it was useful to clean up that list.

Thanks to diesieben07 for this tip. You can get a proper list of all biomes with:

BiomeGenBase[] allBiomes = Iterators.toArray(Iterators.filter(Iterators.forArray(BiomeGenBase.getBiomeGenArray()), Predicates.notNull()), BiomeGenBase.class);

How To Delete A Biome

In earlier versions of Forge there was a GameRegistry.removeBiome() method which no longer exists.  Now you can work directly with the List in the BiomeManager, using normal List methods to remove (or add).

Detecting What Biome Is At A Given Location

If you ever want your code to do something special based on being in a biome, like maybe if you want it to play different background sounds in your custom biome, you can check using the following: worldObj.getChunkFromBlockCoords(blockpos).getBiome(blockpos, worldObj.getBiomeProvider()).getBiomeName(). Of course you might have to replace the worldObj with whatever instance of a World you have available at that point in the code.

Changing The Biome At A Location

Thanks to Choonster for this tip.

Biome ids  are stored in the Chunk#blockBiomeArray field which you can modify directly. This array stores the biome id of at each X, Z position within the chunk, using (Z & 15) << 4 | (X & 15) as the index. So, if you wanted to modify it you could use the World#getChunkFromBlockCoords() method to get the Chunk at the specified position, then use Chunk#getBiomeArray() to get the biome array. Use Biome#getIdForBiome() to get the id of a biome.

Making Sure Random Generation Is Tied To The World Seed

In Minecraft worlds are generated randomly for each new game and each new area explored. However, in computer programming, it is possible to repeat the "random" sequence by starting with the same "seed" value. This is very useful in debugging code but is also fun in Minecraft because if you find a cool world gets generated you can share the seed with your friends and they can generate the exact same world.

Therefore, if you're going to add randomness to any of your custom generation code, you should tie it to the world's seed so they your custom stuff will also be able to be generated the same way each time someone uses the same seed.

To do this, when you need randomness in your custom generation code you should use the public World.rand field and get your random numbers using the appropriate methods for Random class.

Creating Your Own Dimension

See Jimmy04Creeper's Custom Dimensions Tutorial.

Creating An Empty Dimension

You can do this by simply following the steps to make a custom dimension, but in your custom ChunkProvider class @Override the ChunkProvider#provideChunk() method to return an empty chunk.

For example:

public Chunk provideChunk(int parChunkXIndex, int parChunkYIndex)
Chunk chunk = new Chunk(this.worldObj, parChunkXIndex, parChunkYIndex);
return chunk;

Changing How Fast The Day Progresses

You cannot (easily) change the pace of the day in the vanilla because the duration of a day is a fixed value in the WorldProvider#calculateCelestialAngle() method, but you can easily do this in your own dimension. In other words make a custom WorldProvider.

Custom Tree Generation

See Jimmy04Creeper's Custom Trees Tutorial (see second post in thread).

Preventing And Changing Generation Of Decorations (Trees, Cactus, Etc.)

You can handle the DecorateBiomeEvent.decorate event and check for the EventType. If you don't understand how to handle an event, check out Jabelar's Event Handling Tutorial.

Based on the EventType you can do your own thing and use the event.setResult(Result.DENY) to prevent the standard decoration.

Possible EventTypes you can intercept are:

  • CLAY
  • LAKE
  • REED
  • SAND
  • TREE
  • CUSTOM -- it seems this is to allow you to post custom event that can then be handled.

Changing The Contents Of Generated Chests

The ChestGenHooks class has categories BONUS_CHEST and VILLAGE_BLACKSMITH. Register your loot there.

No comments:

Post a Comment