Minecraft Forge 1.7.2/1.7.10 Creating Custom Crops

Introduction


It seems to be fairly common for modders to want to create a custom crop.  In this tutorial I show how to create a seed food type crop (like potato where the crop harvested can be used to plant more), consisting of the seed food item and the plant block.

As a minor twist I wanted my crop plants to render more like a flower, so I show how to control the render type for interesting effect.

The Crop Concept


Basically a "crop" consists of:
  • A seed item (which may also be edible food)
  • A bush block (which needs to be planted in certain places and grows according to time and bonemeal added)
  • A harvest item (which may be the same as the seed item)

Create The Custom Crop Block Parent Class As Extension Of BlockBush


I wanted to create several similar crops (blueberries, strawberries, oranges, tomatoes, etc.) so figured it was wise to create a parent class for them all.  I could have probably just used the vanilla BlockBush as the parent but for more control I often like to create an extension class that gives me the power to @Override anything I want.  This allows me to easily create something special for my mod (in this case rendering the bush like a flower) in one place.  I also like to get really familiar with the code so it can be instructive to go through each method and see if an @Override makes sense for your mod.

Note that choosing whether to extend a class or copy a class is an important decision.  In this case I wanted something like a BlockPotato, but was not intending to make a sub-type of BlockPotato.  So instead of extending BlockPotato I chose to extend the parent BlockBush class.  For more on the topic of extending versus copying see: Jabelar's Extend Or Copy Tutorial.

Note: My examples come from a mod called RecipePlus, so you'll see names that include "Recipe" and "RecipePlus" throughout the code.  Replace those with names that make sense for your mod.

Example: So here is the RecipeBlockCrops class (will be parent to each of my actual crop blocks):


public class RecipeBlockCrops extends BlockBush implements IGrowable
{
    protected int maxGrowthStage = 7;

    @SideOnly(Side.CLIENT)
    protected IIcon[] iIcon;

    public RecipeBlockCrops()
    {
     // Basic block setup
        setTickRandomly(true);
        float f = 0.5F;
        setBlockBounds(0.5F - f, 0.0F, 0.5F - f, 0.5F + f, 0.25F, 0.5F + f);
        setCreativeTab((CreativeTabs)null);
        setHardness(0.0F);
        setStepSound(soundTypeGrass);
        disableStats();
    }

    /**
     * is the block grass, dirt or farmland
     */
    @Override
    protected boolean canPlaceBlockOn(Block parBlock)
    {
        return parBlock == Blocks.farmland;
    }

    public void incrementGrowStage(World parWorld, Random parRand, int parX, int parY, int parZ)
    {
        int growStage = parWorld.getBlockMetadata(parX, parY, parZ) + 
              MathHelper.getRandomIntegerInRange(parRand, 2, 5);

        if (growStage > maxGrowthStage)
        {
         growStage = maxGrowthStage;
        }

        parWorld.setBlockMetadataWithNotify(parX, parY, parZ, growStage, 2);
    }
    
    @Override
    public Item getItemDropped(int p_149650_1_, Random parRand, int parFortune)
    {
        return Item.getItemFromBlock(this);
    }

    /**
     * The type of render function that is called for this block
     */
    @Override
    public int getRenderType()
    {
        return 1; // Cross like flowers
    }
    
    /**
     * Gets the block's texture. Args: side, meta
     */
    @Override
    @SideOnly(Side.CLIENT)
    public IIcon getIcon(int parSide, int parGrowthStage)
    {
     return iIcon[parGrowthStage];
    }
   
    /*
     * Need to implement the IGrowable interface methods
     */

    /*
     * (non-Javadoc)
     * @see net.minecraft.block.IGrowable#func_149851_a(net.minecraft.world.World, 
     * int, int, int, boolean)
     */
    @Override
    // checks if finished growing (a grow stage of 7 is final stage)
    public boolean func_149851_a(World parWorld, int parX, int parY, int parZ, 
          boolean p_149851_5_)
    {
        return parWorld.getBlockMetadata(parX, parY, parZ) != 7;
    }

    /*
     * (non-Javadoc)
     * @see net.minecraft.block.IGrowable#func_149852_a(net.minecraft.world.World, 
     * java.util.Random, int, int, int)
     */
    @Override
    public boolean func_149852_a(World p_149852_1_, Random parRand, int p_149852_3_, 
          int p_149852_4_, int p_149852_5_)
    {
        return true;
    }

    /*
     * (non-Javadoc)
     * @see net.minecraft.block.IGrowable#func_149853_b(net.minecraft.world.World, 
     * java.util.Random, int, int, int)
     */
    @Override
    public void func_149853_b(World parWorld, Random parRand, int parX, int parY, 
          int parZ)
    {
        incrementGrowStage(parWorld, parRand, parX, parY, parZ);
    }
}

Hopefully most of the code above is easy to understand.  Mostly it is setting the things you would expect for a custom block (things like hardness and render type) and for a crop (indicate growth of plant).

You'll see that there are some functions that don't have mappings to human-readable names: func_149851_a, func_149852_a, and func_149853_b.  These are leftovers that never got mapped by MCP, so we have to live with them.  Basically their functions are as follows (thanks to TehSeph for figuring this out!):

  • func_149851_a returns true if bonemeal is allowed, false otherwise.
  • func_149852_a returns true at the same time bonemeal is used if conditions for a growth-tick are acceptable.
  • func_149853_b processes the actual growth-tick logic, which is usually increasing metadata or replacing the block.
The incrementGrowStage() method is very important.  Basically, as bonemeal is added the grow stage increments from 0 to 7.  There is a bit of randomness in how much growth occurs, and it is capped at 7.  This grow stage is used to index the animation of textures that shows the growth of the bush and ripening fruit, and is also used to determine the harvest when the block is broken.

As mentioned before, I wanted my crop blocks to render more like a flower (this render type is called "crossed squares" in the code) where the textures kind of cross each other.  In any custom block you can @Override the getRenderType() method to set the render type, which is what I did.

Tip: For a list of possible render types you should look at the source for the renderBlockByRenderType() method of the RenderBlocks class.  There you will see a switch statement by render type and will see that standard blocks are 0, "crossed squares" is 1, torch is 2, and so on.

Tip: You can even define your own custom render types for blocks.  See: TheGreyGhost's Custom Block Rendering Tutorial.

The getIcon() method is important to @Override because this is what controls the animation of the crop growing and ripening.  It simply returns an icon based on the growth stage (which is passed as the metadata value).

Override updateTick() If You Want Natural Growth


The code above only has crop grow when bone meal is applied.  If you also want the block to grow "naturally" then you'll want to @Override the updateTick() method with something like this:

/**
 * Ticks the block if it's been scheduled
 */
@Override
public void updateTick(World parWorld, int parX, int parY, int parZ, Random parRand)
{
    super.updateTick(parWorld, parX, parY, parZ, parRand);
    int growStage = parWorld.getBlockMetadata(parX, parY, parZ) + 1;

    if (growStage > 7)
    {
        growStage = 7;
    }

    parWorld.setBlockMetadataWithNotify(parX, parY, parZ, growStage, 2);
}

Create Specific Custom Blocks As Extensions Of Your Custom Crop Parent Class


Example: I created a BlockBlueberry that extends RecipeBlockCrop.

public class BlockBlueberry extends RecipeBlockCrops
{

    public BlockBlueberry()
    {
        // Basic block setup
        setBlockName("blueberries");
        setBlockTextureName("recipeplus:blueberries_stage_0");
    }

    /**
     * Returns the quantity of items to drop on block destruction.
     */
    @Override
    public int quantityDropped(int parMetadata, int parFortune, Random parRand)
    {
        return (parMetadata/2);
    }

    @Override
    public Item getItemDropped(int parMetadata, Random parRand, int parFortune)  
    {
     // DEBUG
     System.out.println("BlockBlueberry getItemDropped()");
        return (RecipePlus.blueberry);
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public void registerBlockIcons(IIconRegister parIIconRegister)
    {
          iIcon = new IIcon[maxGrowthStage+1];
          // seems that crops like to have 8 growth icons, but okay to repeat actual texture if you want
          // to make generic should loop to maxGrowthStage
          iIcon[0] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_0");
          iIcon[1] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_0");
          iIcon[2] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_1");
          iIcon[3] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_1");
          iIcon[4] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_2");
          iIcon[5] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_2");
          iIcon[6] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_3");
          iIcon[7] = parIIconRegister.registerIcon("recipeplus:blueberries_stage_3");
    }
}

Again the code should be fairly self explanatory.

Note that the item that is returned is specified in the getItemDropped() method.  It is important to return the instance of your food item that is registered instead of a new instance (otherwise things like textures will be messed up).

Also, for the quantityDropped() method I wanted more food to drop depending on how much the bush had grown.  Since the block's metadata represents the growth of the bush, I take that (passed in as a parameter) and divide by two since I want a maximum of 3 food dropped (metadata maximum value is 7, so 7 divide by 2 converted to int is 3).  If you're using metadata to control the drop quantity, make sure you use the version of the quantityDropped() method that passes metadata as a parameter.

Another thing of interest is the icon registration. As mentioned above, the crop has 8 (0 to 7) stages of growth possible, and allows for visual effect of growth animation by changing the texture. As you apply bonemeal to the crop, it will advance in growth and show different texture.

I was lazy so only made 4 different textures, but it is okay to repeat some (animation will just be less smooth) in this registration. Graphically, I made each sequential texture show a larger bush and showed the blueberries changing color as they ripen.

Create Custom Item Seed Foods As Extensions To ItemFood Implementing IPlantable


So you also need a seed, and in my case I wanted seeds that can also be eaten as food.  In Minecraft that is called an ItemSeedFood.  Again, I wanted to create several seed foods, so I first recreate a custom class that will be parent of all the rest, with the parent extending ItemFood (making it edible) and implementing IPlantable (making it a seed).

Note: If you don't want it to be a food, you could just extend Item while implementing IPlantable.

Example: RecipeItemSeedFood class:

public class RecipeItemSeedFood extends ItemFood implements IPlantable
{
    private final Block theBlockPlant;
    /**
     * Block ID of the soil this seed food should be planted on.
     */
    private final Block soilId;

    public RecipeItemSeedFood(int parHealAmount, float parSaturationModifier, 
          Block parBlockPlant, Block parSoilBlock)
    {
        super(parHealAmount, parSaturationModifier, false);
        theBlockPlant = parBlockPlant;
        soilId = parSoilBlock;
    }

    @Override
    public boolean onItemUse(ItemStack parItemStack, EntityPlayer parPlayer, 
          World parWorld, int parX, int parY, int parZ, int par7, float par8, 
          float par9, float par10)
    {
     // not sure what this parameter does, copied it from potato
        if (par7 != 1)
        {
            return false;
        }
        // check if player has capability to edit
        else if (parPlayer.canPlayerEdit(parX, parY+1, parZ, par7, parItemStack))
        {
            // check that the soil block can sustain the plant
            // and that block above is air so there is room for plant to grow
            if (parWorld.getBlock(parX, parY, parZ).canSustainPlant(parWorld, 
                  parX, parY, parZ, ForgeDirection.UP, this) && parWorld
                  .isAirBlock(parX, parY+1, parZ))
            {
             // place the plant block
                parWorld.setBlock(parX, parY+1, parZ, theBlockPlant);
                // decrement the stack of seed items
                --parItemStack.stackSize;
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    @Override
    public EnumPlantType getPlantType(IBlockAccess world, int x, int y, int z)
    {
        return EnumPlantType.Crop;
    }

    @Override
    public Block getPlant(IBlockAccess world, int x, int y, int z)
    {
        return theBlockPlant;
    }

    @Override
    public int getPlantMetadata(IBlockAccess world, int x, int y, int z)
    {
        return 0;
    }

    public Block getSoilId() 
    {
        return soilId;
    }
}

The code again should be self explanatory.  The constructor combines aspects of food (heal amount, saturation modifier) and plant (plant block association, soil type).

When the seed food is used (onItemUse() method), we make the expected checks (can player do modification, is there right soil type on ground, is there space for the plant to grow) and then place the plant if allowed.

Tip: The getPlantType() method is quite important.  It is checked by the canSustainPlant() method in the Block class, and if it fails your plant will wither/die.

The plant types are enumerated in the EnumPlantType class and are used (according to the comments in the canSustainPlant() method) as follows:
  • Reeds check if its a reed, or if its sand/dirt/grass and adjacent to water
  • Cacti checks if its a cacti, or if its sand
  • Nether types check for soul sand
  • Crops check for tilled soil
  • Caves check if it's a solid surface
  • Plains check if its grass or dirt
  • Water check if its still water

How Jumping On Crops Can Destroy Them


In case you're wondering, there is no direct code in the classes for having mobs grief your crops or for them to be destroyed by player jumping on them.  Instead, that functionality actually happens on the farmland block.

Actually it is a bit more complicated than that. The onFallenUpon() is the right method but not in the crop itself rather on the farmland block. The code in BlockFarmland is:

/**
* Block's chance to react to an entity falling on it.
*/
public void onFallenUpon(World p_149746_1_, int p_149746_2_, int p_149746_3_, 
      int p_149746_4_, Entity p_149746_5_, float p_149746_6_)
{
    if (!p_149746_1_.isRemote && p_149746_1_.rand.nextFloat() < p_149746_6_ - 0.5F)
    {
        if (!(p_149746_5_ instanceof EntityPlayer) && !p_149746_1_.getGameRules()
              .getGameRuleBooleanValue("mobGriefing"))
        {
            return;
        }

        p_149746_1_.setBlock(p_149746_2_, p_149746_3_, p_149746_4_, Blocks.dirt);
    }
}

Basically, player can always cause this, and mobs can cause this if mob griefing is enabled. There is random chance of turning the farmland to regular dirt.

Note: If you're making a plant that grows on a custom block (not built-in farmland), if you want similar behavior you'll need @Override the onFallenUpon() method in that custom block.

Now the interesting thing is that it doesn't immediately destroy the crop (if any) on that farmland. The destruction of the crop presumably happens because dirt cannot sustain the crop and so the crop's check for "can sustain" is key to having it destructed. Each plant block (anything that is subclassed to BlockBush, including extensions of BlockCrops) should have a canBlockStay() method that checks for sustainability. You probably can't rely on the built-in method because it checks whether the block below "thinks" it can sustain your plant but that may depend on a plant type check.

You could try to affect whether the sustain plant method returns false, but it is a bit complicated and involves checking the plant type and the can place on methods. The vanilla coding is meant to be flexible, but since you know exactly what your crop should do Instead to do it more simply you'll can just check directly.

So basically you should @Override the canBlockStay() method in your custom crop block class. In that method, you should check that the block below yours is proper block types to sustain your plant. If you want it to die is it is jumped on, you need to make sure that if the block below is regular dirt that you return false.

Create Your Specific Custom Crop Seed Food Classes


Now you can create all your various seed foods.  It is pretty much just extending the parent class and changing the things like textures and names that are specific to your seed.

Example: The ItemBlueBerry that extends RecipeItemSeedFood class:

public class ItemBlueberry extends RecipeItemSeedFood 
{

    public ItemBlueberry() 
    {
        super(1, 0.3F, RecipePlus.blockBlueberry, Blocks.farmland);
        setUnlocalizedName("blueberry");
        setTextureName("recipeplus:blueberry");
        setCreativeTab(CreativeTabs.tabFood);
    }
}

Hopefully that is straight forward to understand.  You would change pretty much everything here to suit your particular seed food. (The 1 represents the healing amount of the food, and the 0.3F represents the saturation modifier.  Feel free to change those for your need.)

Instantiate The Blocks


So you have your classes, and now we need to make instances.  For the block, in your mod's main class' field declarations you would add the instantiation. Something like this:

public final static Block blockBlueberry = new BlockBlueberry();

Register The Blocks


Don't forget to register your blocks.  You'd do this where you register other block: either in your mod's main class or the common proxy class, during the "preInit" stage of FML life cycle.  For example:

GameRegistry.registerBlock(blockBlueberry, "blueberries");

Tip: I used the plural "blueberries" for the block representing the plant.  I use the singular "blueberry" for the item.  That is just a naming convention from the v, and you can do it differently, but I found it useful.

Instantiate The Items


Also need to instantiate the items.

Warning: It is extremely important to ensure that the crop block is instantiated before the related seed item.  I've seen a lot of people get null pointer exception errors by declaring the block without initializing it (i.e. it is null) and then instantiating the seed.  The problem is that once you pass the null as the block, instantiating the block later will not update the null and when you run your mod and plant your seed you'll get a null pointer exception.

In your mod's main class' field declarations you would add the instantiation.  Something like this:

public final static Item blueberry = new ItemBlueberry();


Register The Items


Don't forget to register your items.  You'd do this where you register other block: either in your mod's main class or the common proxy class, during the "preInit" stage of FML life cycle.  For example:

GameRegistry.registerItem(blueberry, "blueberry");

Create And Import The Texture Assets


The main assets required are the various textures, and the lang file (to ensure the display names are easy to read, and localized if necessary).

Textures


Texture assets should be placed in standard locations.  In my case I created a package called assets.recipeplus.textures.blocks in the src/main/resources folder and imported my PNG texture files there.

Key Point: The one thing I want to mention that is special for crops is that you need to create several textures (up to 8) for each custom crop block.  These should show the plant growing and the crop ripening, to create a sort of animation.  These are registered in the registerBlockIcons() method of the parent crop block class, and you should register index 0 to 7.  Go back and review my example near top of this tutorial.

Tip: I followed the naming convention for potato and gave the block textures names like, blueberries_stage_0blueberries_stage_1, etc. to represent the stages of growth.

Update The Lang File


Hopefully you understand how to use a lang file, I won't explain that here.  But to complete your mod you should add the names of both your plant block and the seed food item to the lang file.  For example:

tile.blueberries.name=Blueberries
item.blueberry.name=Blueberry

Conclusion


Hopefully this inspires you to try making your own custom crops.  As always, feel free to contact me with suggestions on corrections and clarifications to make this tutorial better.

24 comments:

  1. Anyway you can do a video tutorial on this please?

    ReplyDelete
  2. how would you go about allowing the seed to be planted on still liquid such as water?

    ReplyDelete
    Replies
    1. In the canPlaceBlockOn method, replace the line: "return parBlock == Blocks.farmland;" with "return parBlock == Blocks.water;" if you want still water. Or replace water with whatever block you want...even gold_block (which, of course, should allow you to plant the crop on a block of gold o_O).

      Delete
  3. What about making the crop or bush generate on world generation? Like so we can have the bush
    already in the world?

    ReplyDelete
  4. When I break the crop block it doesn't drop anything no matter the growth state

    ReplyDelete
    Replies
    1. The getItemDropped() and getQuantityDropped() methods control this. To debug the problem, you might want to change the getQuantityDropped() to always return 1 so it doesn't get impacted by metadata.

      Also, in 1.8 you need to make sure you make proper 1.8 method to get the metadata value from the property. So it is possible your metadata value isn't being passed properly. I haven't updated this tutorial for 1.8, so you'll have to look at 1.8 block tutorial to figure that part out.

      Delete
    2. never mind I was trying to break the plant in creative mode, which doesn't drop the item so everything is fine.

      Delete
  5. Eclipse doesn't like the @Override annotation over onItemUse() in the seed. I have confirmed spelling and confirmed that Item is imported properly. Thoughts?

    ReplyDelete
    Replies
    1. For @Override to work, you have to make sure your class extends ItemSeed (or at least some sort of Item class). Secondly, all the parameters must be exactly the right type. Third the names of the methods have to be properly mapped (this is done when you build your workspace, in 1.8 it is often good idea to explicitly put in a mapping). Lastly, method names can change between Forge versions -- what version are you using?

      The other thing you should do is use Eclipse to look at the Type Hierarchy for your seed class. That will list all the methods available at all the levels, and perhaps you will see if something looks wrong there.

      Delete
    2. My seed is extending Item (as it isn't eatable) and I even made a test item to double-check the name and parameters of the function - but it still doesn't work. I'm using (not sure of the exact version) forge for 1.7.10, which means no function name difference anyway. And if the parameters were wrong, Eclipse would tell me. I will try the last bit when I get home.

      Delete
    3. If Eclipse is telling you the @Override is bad, it means either the function name or the parameter list is bad. So such an error IS telling you that the parameter list is bad. As a test, what happens if you copy my code into your class?

      Delete
  6. My plant doesn't drop seeds... Do I have to make another method for that or how can I extend the getItemDropped method for another Itemdrop?

    ReplyDelete
    Replies
    1. Did you ever figure this out? I want this as well.

      Delete
  7. It seems I need to fix the @Override for EnumPlantType so I can make my own Enum value for my custom blocks. I want custom soil basically. How would I achieve this?

    ReplyDelete
    Replies
    1. Also, when I plant my "seed" on tilled soil (since I haven't been able to override this yet) it appears the plant is at full growth as I can harvest it by punching it instantly to get the drop I want.

      How do I have multiple drops from the plant, 1 based on random chance.

      Finally, my textures don't seem to work.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. I am having the same two problems as you, but on the middle one, what you do is add an ArrayList. Like this:

      @Override
      public ArrayList getDrops(World world, int x, int y, int z, int metadata, int fortune){
      ArrayList drops = new ArrayList();

      //Insert your item's path in the category ITEMX, and under world.rand.nextInt, put one number in the parentheses and one being added to that, and those two will be the outside numbers of your drop. For the first line, it will drop 1, 2, or 3 of ITEM1, and for the second line, it will always drop 1 if ITEM2.

      drops.add(new ItemStack(ITEM1, world.rand.nextInt(3)+1));
      drops.add(new ItemStack(ITEM2, world.rand.nextInt(1)+1));
      return drops;
      }

      Delete
    4. This comment has been removed by the author.

      Delete
  8. Is there any way to create a new Enum material if you want to use a specific/new block?

    ReplyDelete
  9. Can you make a tutorial for 1.8? It gives me a lot of errors

    ReplyDelete
  10. My crop wont break when its trampled on?

    ReplyDelete
  11. I have two problems;
    (A) My textures refuse to show up.
    (B) The crop is planted at level 8 for some reason, so it gives you the items on break.
    I have the source code on the GitHub at https://github.com/mcdermer/tea-time
    Any and all help is apppreciated!

    ReplyDelete
  12. work that with more than 8 textures too ? i mean a plant higher than 1 block e.g tallgrass with 10 stages or more. works it ?

    ReplyDelete