Minecraft Modding: Custom Dimension

Background


Note: Before proceeding see my general discussion on how dimensions work.

This tutorial will create a "cloud" dimension that includes:
  • A "cloud" world type that shows up in the new world menu.
  • A "cloud" block to form the base block for the terrain.
  • A custom single-biome chunk generator.
  • A biome decorator that provides custom "cloud" trees, grass and flowers.

Example Custom Dimension Code: The code for this tutorial can all be found in Jabelar's Example Mod which includes a custom "cloud" dimension. You are welcome to copy the code but please try to understand it!

Recommended Approach


A custom dimension can range from being a simple tweak or a complete overhaul. But to get started generally you'll want to at least do the following steps:

Warning: The classes are all interrelated so you need to make sure they all properly reference each other. For example, the world provider has a method to get the biome provider but also has a method to get a world type which has its own method to get the biome provider. So you need to go through and make sure everything matches.

  1. Create a class to collect instances and registry methods. I recommend making a class called something like ModWorldGen to put instances of things like the dimension type, along with methods for registering the dimension and dimension type.
  2. Create world provider: Create a custom world provider as a class that extends WorldProvider.  
  3. Create dimension type with link to world provider: Create a custom dimension type using the DimensionType.register() method that passes in a new instance of the world provider class you created in Step #1. I recommend that you use the same dimension ID as you will use for registering the dimension to the DimensionManager, and you can find a free ID by looping through the DimensionManager to see if the value is already used. See section below where I give example code.
  4. Create chunk generator: Create a custom chunk generator class the implements IChunkGenerator. You probably want to copy code from ChunkGeneratorOverworld as a starting point to play around with. This class can get be as simple or as complicated as you want. You can choose to generate random height maps, use map gen classes to create things like caves and ravines, use ore gen classes to populate mineable veins, use biomes to decorate the top layers, use structures to generate villages and similar. Go crazy!
  5. Link chunk generator to world provider: In the your world provider class from Step #1, make sure you @Override the createChunkGenerator() method to return a new instance of the chunk generator class you created in Step #3.
  6. Register dimension: In a pre-init loading event you need to register your dimension in the dimension manager using the DimensionManager.registerDimension() method. I recommend using the same ID you used for the DimensionType.register() method.
  7. Create custom biome(s) if desired.
  8. Create custom biome decorators if desired.
  9. Create custom world generators if desired.
  10. Create a biome provider.
  11. Register biomes: If your chunk generator calls on custom biomes you need to register those as well:
    1. Handle the biome registry event: Registering biomes occurs in a RegistryEvent handler. 
    2. Add biome: Use BiomeManager.addBiome() to add your custom biomes.
    3. Configure other biome settingsBiomeManager methods such as addSpawnBiome()addStrongholdBiome(), and so forth may be needed for your custom biome. Look at the BiomeManager Type Hierarchy in Eclipse to see all available methods.
    4. Add types to your biome: Use BiomeDictionary.addTypes() method.
  12. Register world generators if necessary: If your chunk generator calls on world generator classes similar to shrines, those need to be registered in the init loading phase using GameRegistry.registerWorldGenerator() method.
  13. Create a custom world type: If you want your dimension to be an option from the New World menu you will need to do this. Also to really customize a dimension you should probably make one. Basically you need to make a custom world type class that extends WorldType. You can control things like sea levels, cloud heights and stuff. You should then make sure your world provider class returns this for the getTerrainType() method. 
  14. Tie it all together. Make sure all the classes reference each other properly.
  15. Keep going: you can go as crazy as you want with custom structures, trees, decorators, and so forth.

Warning: I highly suggest you you start simple and thoroughly test before adding more complexity.

Create A Class To Collect Instances And Registry Methods


In order to refer to the various instances you create, it is good to collect the instances in a class for organization. Further, I tend to put the registration methods here too.

Tip: I recommend using same ID for both dimension type and the actual dimension, and I make sure the dimension ID is free before using it -- see method in my example code.

Create A Custom WorldProvider


Basically you need to make a new class that extends WorldProvider.

Warning: At a minimum is important to @Override the getDimensionType() (note not the getDimension() method which you should leave alone) and getTerrainType() to point to the custom types you will create below. 

Additionally, you should look at the other methods available and compare with the vanilla providers to get ideas on customizing your world. For example, there are methods for controlling how the sun and moon move, whether the map spins, and so forth. For example, in my dimension I choose to make it unable to rain or snow, so I @Override the related methods. Have fun customizing.

See my example code.

Create And Register A Custom DimensionType



The dimension type basically associates a dimension ID with a world provider. You can create it with a single line in your collection class created above. For example: public static final DimensionType CLOUD_DIM_TYPE = DimensionType.register(CLOUD_NAME, "_"+CLOUD_NAME, CLOUD_DIM_ID, WorldProviderCloud.class, true);

Note: I'm both instantiating and registering the dimension type.

See this in the example code.

Create A Custom IChunkGenerator


A custom chunk generator should implement IChunkGenerator interface, and specifically you need to @Override the generateChunk() method.

How you implement the generateChunk() method is up to you. But the main point is that all the blocks in your chunk should get placed. Normally this is done in several steps such as:
  1. Use noise functions to generate a "height map". Basically this defines the shape (somewhat random) for the surface of your world.
  2. Set the base blocks in the chunk by taking the height map and filling it in with a base block for your dimension. Usually oceans are also created here by filling in any area where the height is less than the sea level.
  3. Add the biome-specific stuff by calling the genTerrainBlocks() method.

Register Dimension


The dimension type was already registered when it was created above. But you also need to register the dimension itself. 

Tip: I highly recommend using the same ID for both the dimension type and dimension registration

In the "pre-init" loading method for your mod, you should register your dimension by simply calling the DimensionManager.register() method and passing an ID and the dimension type instance you created previously.


Create A Custom Biome


Your custom biome class should extend Biome. The biome class needs to do three things:
  • Generate blocks: Provides a genTerrainBlocks() method to re-surface the chunk with biome-specific blocks.
  • Decorates: @Override the createBiomeDecorator() method to return your custom decorator (discussed below) along with related settings to control the decoration. Also usually need to @Override the getRandomTreeFeature(), getRandomWorldGenForGrass() and the pickRandomFlower() methods.
  • Spawns entities: Indicates what entities are spawnable.
The genTerrainBlocks() can be as simple or complicated as you want. It usually adds a bedrock layer (with a bit of randomness) as well as re-surfacing the terrain with the top blocks.

We'll discuss the biome decorator in detail below.

A biome has a number of "spawnable" lists (regular as well as for water and caves) which should be set up in the biome constructor.


Create A Custom BiomeDecorator


Your decorator class should extend BiomeDecorator and at a minimum should @Override the decorate() method.

A biome decorator is a very important class and usually calls all of the "world gen" feature generation such as:
  • Ore generation
  • Trees
  • Grass
  • Flowers
  • Mushrooms
  • Reeds
  • Cactii
  • Water lilies
  • etc.
Usually there is a world gen class for each of the types of things to generate. You can mix vanilla and custom world gen types -- in my example mod I use vanilla ore generators but use custom trees, grass and flowers.

See example code.

Create Custom World Gens, Structures and Features


There are several different ways in which features are generated. There are MapGenBase sub-classes that make things like swamp huts, there are Structure sub-classes that make things like villages, there are WorldGenerator sub-classes that make things like trees, and there are IWorldGenerator implementations which is a Forge interface with registry for such things. There is also a Forge registry for Village-related stuff.

It is a very open-ended subject, and you can simply call any or all of these from your biome decorator class. But there are some standard ways of doing some things, which I will try to explain here.

Register Your World Gens, Structures and Features


Many of the generators need registration of some sort:
  • Structures: In the init loading phase of your mod, use the MapGenStructureIO.registerStructure() method to register the structure. Similarly use the MapGenStructureIO.registerStructureComponent() method tor register any structure pieces
  • Villages: In addition to registering it as a structure (see above) you need to use the VillageRegistry. See Jabelar's Custom Village Tutorial.
  • IWorldGenerator: In the init loading phase of your mod, use the GameRegistry.registerWorldGenerator() method to register your generator.

Create A Custom BiomeProvider


Tip: I suggest that for your first custom dimension you stick to a provider that provides a single biome.

Single Biome Provider

To do this all you have to do is @Override your custom world type's getBiomeProvider() method to return a new instance of BiomeProviderSingle where you pass your registered custom biome instance from above to the constructor.

Multi-Biome Provider (Semi-Custom)

If you really want to make a dimension with multiple biomes you have to @Override your custom world type's getBiomeProvider() method to return a new instance of BiomeProvider where you additionally:
  • Replace the public field allowedBiomes with your own List which can contain a mix of your custom biomes and vanilla biomes.
  • Replace the public field biomesToSpawnIn with your own List which can contain a mix of your custom biomes and vanilla biomes.
Warning: For this approach to work, your biomes need to be properly typed in the BiomeManager as well as the BiomeDictionary. In other words, things like whether the biome is cool and dry, etc.

Multi-Biome Provider (Full Custom)

The vanilla BiomeProvider class used in the semi-custom approach above implements a specific algorithm for selecting and blending biomes. If you wish to have more control to do something different you will need a full custom provider. The steps would be:
  1. Create a class that extends BiomeProvider and @Override all the functionality you want.
  2. @Override your custom world type's getBiomeProvider() method to return a new instance of your custom provider.

Register Your Biome(s)


You need to create an event handling method to handle the RegistryEvent.Register event. You should also use the @ObjectHolder method for injecting registered instances into your static instance fields. See example code.

Important: To fully register you biome, in addition to the registry event you need to ensure your fully registered in the BiomeManager and BiomeDictionary to indicate the type (COOL, DRY, etc.) and what can spawn.

In the init loading phase of your mod you should:
  1. Call BiomeManager,addBiome() method to add your biome with associated BiomeType and generation "weight".
  2. Call BiomeManager.addSpawnBiome() if you want your biome to be a place where player can spawn.
  3. Call BiomeManager.addStrongholdBiome() and addVillageBiome() if you want strongholds and villages to generate in your biome.
  4. Call BiomeDictionary.addTypes() to associate your biome with BiomeDictionary.Type.

Create A Custom WorldType


The WorldType is the primary class that associates everything together. How it all works is as follows:

  1. Your custom DimensionType is registered with the DimensionManager to associate with your custom WorldProvider which has (in the parent class) a private terrainType field (which is of type WorldType). 
  2. When a WorldClient or WorldServer is constructed, it will:
    • Use the dimension ID and get the DimensionManager to create the associated WorldProvider.
    • Use the WorldProvider#setWorld() method which will (among other things) assign the terrainType field to an instance of your custom WorldType.
  3. From the WorldType the WorldProvider will get the IChunkGenerator and BiomeProvider, as well as some other aspects for the world (like the average ground level, the void fog fade factor, cloud height and spawn "fuzz" randomness).


Tip: If you don't want your world type to show up in the create new world GUI you should @Override the canBeCreated() method to return false.

Note: You do not need to explicitly register your custom world type because in the WorldType constructor your custom type will automatically be added to the WORLD_TYPES array which is used for the create new world GUI.

To create a custom WorldType:

  1. Create a custom WorldType class: Create a new class that extends WorldType. At a minimum @Override the getBiomeProvider() and getChunkGenerator() methods, and look at the other methods in the WorldType class to see if any other methods are relevant to modify. See example code.
  2. Create an instance of your world type: Somewhere such as in an init class where you collect instances, or even your main mod file, you should create an instance of your custom world type. This will also automatically register your custom world type in the WorldType.WORLD_TYPES array. See example code.

Update Your .lang File(s) For Localized Text


The name of your WorldType will be displayed in the create new world GUi. Additionally you can add "info" to display in that GUI. Therefore, like all UI text it is recommended you make .lang file entries accordingly:
  • Your world type string will have a key of generator.
  • Your world type info string will have a key of generator..info

Tie It All Together


The Minecraft code for world generation is quite complicated. Because of the complication, and also because you are likely to have copied code from the vanilla classes, it is very important that you have fully tied it together (that your custom classes are all working together).

Warning: It is very easy to miss something in terms of registration or in having your classes reference each other, especially if you copied vanilla code. Take the time to fully check everything.

At a minimum I recommend that you confirm that:

  • Your custom DimensionType instance properly associates to your custom WorldProvider class.
  • Your custom WorldProvider class properly overrides the getDimensionType() method to return your custom DimensionType. (This is an example of the convoluted code -- you have to associate both direction!)
  • Your custom WorldType class properly overrides the getBiomeProvider() and getChunkGenerator() methods to return your custom BiomeProvider and IChunkGenerator respectively.
  • Your custom Biome class properly overrides the getBiomeDecorator() method to return your custom BiomeDecorator.
  • Your custom Biome is properly instanced and registered using the RegistryEvent.Register event and @ObjectHandler injection method.


Testing


As always it is recommended to thoroughly test your mod. I suggest you at a minimum test the following:

  • World type selection: Check that your world type shows up in the new world creation GUI. The name should be properly localized and any "info" you put in your .lang file should be displayed.
  • Crash-free generation: Your dimension should generate without crashing. Obviously debug any such crashes. I also recommend sprinkling your code with console statements (System.out.println()) in order to get a better understanding of how the world generation executes.
  • Check that each of your biomes generates. If you press key while playing, a "debug" overlay comes up that indicates which biome you are in (among other things). Wander around to find your biome. I highly suggest putting console statements in you generators that print out the location of the generation, then you can watch your console and simply teleport to the location indicated.
  • Check that each of your features and structures generates: Wander your world to find everything. I highly suggest putting console statements in you generators that print out the location of the generation, then you can watch your console and simply teleport to the location indicated.
Depending on your other customizations, find ways to test them.

Conclusion


World generation is quite complicated, but if you take a structured approach and try to understand how ti works you can makes some of the most meaningful and impressive modifications to Minecraft. As always, I look forward to your comments. Happy modding!

2 comments:

  1. Thank you very much! A great tutorial, which helped me a lot!
    I have a question though: For my first try I copied most of the actual chunk generation from the Nether. In fact it is more or less identical and I haven't generated any blocks myself yet. However I keep getting "Cascading worldgen" warnings, and I am wondering if this could be a problem with the actual vanilla generation in the nether (as I haven't changed it) or is it my implementation? Thank you in advance

    ReplyDelete
  2. Thanks, this is very useful! But do you have a 1.12 tutorial about the sky? I want to change the size of the sun and the moon, change their textures, remove the clouds in a dimension and use my own "end-sky like" texture for another dimension without changing the Vanilla end, but the tutorial you were doing about it is obsolete and no longer works for 1.12 ... (Or I am dumb and I didn't succeed)

    ReplyDelete