Tuesday, March 3, 2015

Minecraft Tutorials: Adding To World Generation

This is a continuation of the my minecraft modding tutorial.  In this post I'll discuss how to add a new ore to world generation.  This tutorial assumes you are at least familiar with creating a basic mod file, and creating a block.  The code I will be showing is built on all of my previous tutorials so if you haven't been following along your code may be slightly different, which is ok.

If you have been following along, you'll remember that in our block tutorial we created a copper ore block, but up until now we've had no way to obtain this block other than in creative mode, or through cheat commands.  Now we will add a WorldGenerator which will cause our copper ore to spawn naturally in the world. To do this I will create a new class that implements the interface IWorldGenerator. Since this is likely the only WorldGenerator we will have in our mod I will place it in my main mod package, but if you had other classes that affected world generation, such as new biomes, you might put it in a world sub-package or something.

I'll name my class WorldGenGinderMod. At this point I have an error because I must implement an inherited method called generate.  My generate method looks like this:
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider)
{
       switch(world.provider.dimensionId)
       {
       case 0:
              break;
       case 1:
              break;
       case -1:
              break;
       default:
              break;
       }
      
}

The switch statement takes the dimensionid of the current world provider, we will be using the 0 case only, because 0 is the id for the Overworld. If we wanted to generate in the Nether we would use -1, or 1 for the end.  If we had our own custom dimension it would have its own unique id that we could use.  Techinically, we could forgo the switch statement all together because the way we will generate our ore is by replacing stone blocks, but using this switch prevents our code from running when it definitely isn't needed.

Before adding any more code to generate() I'm going to write another method that does the generating, this way if we wanted to reuse the method for other things, like generating a different ore, we could do so without repeat code.  This method will be called generateOre, and it is a bit more complex than any code I've discussed so far, so please bear with me.  It looks like this:
private void generateOre(Block newBlock, Block oldBlock, World world, Random random, int blockPosX, int blockPosZ, int minVeinSize,
              int maxVeinSize, int spawnChance, int minY, int maxY )
{
        WorldGenMinable minable = new WorldGenMinable(block, (minVeinSize + random.nextInt(maxVeinSize - minVeinSize)), Blocks.stone);
        int posX;
        int posY;
        int posZ;
        
     for(int i = 0; i < spawnChance; i++)
     {
         posX = blockPosX + random.nextInt(16);
         posY = minY + random.nextInt(maxY - minY);
         posZ = blockPosZ + random.nextInt(16);
         minable.generate(world, random, posX, posY, posZ);
     }
}

This method takes a *ton* of parameters, but names should be fairly self explanatory, so lets jump in to what it's actually doing. 

First we add a WorldGenMinable object, this is what actually does the replacing of stone with our copper ore.  Its constructor takes three parameters, the block we want to have spawn, the number of blocks to replace, and the block we are replacing. I pass newBlock to the first parameter, and oldBlock to the third parameter. Since we want the amount of blocks in a vein to be kind of random, we use (minVeinSize + random.nextInt(maxVeinSize - minVeinSize)) to generate a random number between our minimum and maximum vein sizes and pass this to the second parameter. If later we want to have generate ore with a fixed vein size we can do this by making the minVeinSize and maxVeinSize parameters equal.

Next we declare some ints for use in our for loop.  The for loop increments through until it reaches the spawnChance we passed, so it will continue to run longer for higher values of spawnChance.  In the loop we generate some semi-random coordinates and then pass them to the generate method of our WorldGenMinable object minable, this method will do all the replacing, and thankfully someone else has already written it for us so we don't even have to look at it! If you would like to see the declaration and you are using eclipse you can highlight it and hit F3, I warn you, it is a lot of maths.

Now we want to come back to *our* generate() method and add a call to our new method under case 0:
this.generateOre(ModBlocks.copperOre, Blocks.stone, world, random, chunkX*16, chunkZ*16, 10, 15, 10, 0, 90);

We pass ModBlocks.copperOre in the first parameter, since this is the new block we want to have spawn. We are replacing Blocks.stone, so its only going to spawn in stone, then we pass the world and a our random object (no sense in generating another one), multiplying chunkX and chunkZ by 16 converts them from chunk coordinates to block coordinates. Next we pass our min and max vein sizes, 10 and 15 will make our veins about the size of a vein of coal.  We pass 10 for our chance to spawn, and then the last two parameters dictate what y levels our ore will spawn, 0 to 90 means that it will spawn down until it reaches bedrock and up to a height of 90, so it will be seen in some mountain ranges.

My whole WorldGenGrinderMod class looks like this:
package me.codasylph.grindermod;

import java.util.Random;

import me.codasylph.grindermod.blocks.ModBlocks;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.feature.WorldGenMinable;
import cpw.mods.fml.common.IWorldGenerator;

public class WorldGenGrinderMod implements IWorldGenerator {

       @Override
       public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider)
       {
              switch(world.provider.dimensionId)
              {
              case 0:
                     this.generateOre(ModBlocks.copperOre, Blocks.stone, world, random, chunkX*16, chunkZ*16, 10, 15, 10, 0, 90);
              case 1:
                     break;
              case -1:
                     break;
              default:
                     break;
              }
             
       }

       private void generateOre(Block newBlock, Block oldBlock, World world, Random random, int blockPosX, int blockPosZ, int minVeinSize,
                     int maxVeinSize, int spawnChance, int minY, int maxY )
       {
               WorldGenMinable minable = new WorldGenMinable(newBlock, (minVeinSize + random.nextInt(maxVeinSize - minVeinSize)), oldBlock);
               int posX;
               int posY;
               int posZ;
               
            for(int i = 0; i < spawnChance; i++)
            {
               posX = blockPosX + random.nextInt(16);
                posY = minY + random.nextInt(maxY - minY);
                posZ = blockPosZ + random.nextInt(16);
                minable.generate(world, random, posX, posY, posZ);
            }
       }
}

If later I wanted to add different ore I would just make another call to generateOre under the case for the dimensionId I wanted it to spawn in.

Last, but certainly not least, I need to register my WorldGenerator, this will be done in the main mod class.  You could make a separate class that handles this, but because most mods will only have one WorldGenerator there isn't as much of a reason to do so. I will just add a call to the registration method in my preInit.  The line looks like this:
GameRegistry.registerWorldGenerator(new WorldGenGinderMod(), 0);

It takes two parameters, an object that implements IWorldGenerator, and a weight, the weight determines when the generator will run, the higher the number the later they run.

Now if you run the testing environment you should be able to track down some copper ore! Please note that chunks that have already been generated prior to adding this code won't contain any ore, you'll have to create a new world or at least generate new chunks to find the ore.

Next tutorial? Recipes!

You may return to the table of contents here.

No comments:

Post a Comment