Plants

From Minecraft Forge
Jump to: navigation, search


How-To Icon.png

This is a How-To guide or Tutorial detailing a practice or process for Minecraft Forge or related software.

Generic Mod

Havvy Generic Mod Banner Final.png

October 9, 2014 Added metadata support in localising tutorial --SkylordJoel

March 27, 2014 Updated Basic Items to 1.7.x --Jwosty

February 13, 2014 1.7 update happen and I fix up some of the stuff so far:

But it has one part not working yet

--Chibill

July 5, 2013

1.6.1 came out so I have fixed up some of the basic tutorials to go with it.

Ones that are updated include:

-- Mew

March 13, 2013

1.5 came out, so I fixed the methods to account for this (mostly the Icons/Textures tutorial).

--Squawkers13

December 13th, 2012

Alright, Forge made their installation process really simple and also made proper packages, removing the common/src distinction. I rewrote most of Basic Modding to account for this update. Overall though, this simplifies the explanations, but imports changed in all tutorials. Next up, I need to finish the world gen tutorial.

With the Basic Modding tutorial being mostly rewritten, if anything is confusing about it now, please tell me ASAP!

December 3rd, 2012

I finally finished the tutorial on Plants I started a month ago. I also fixed a minor compilation bug in Packet Handling. I reorganized the Generic Ore section by adding a Dropping Ore section. I'm starting to use a General Knowledge template where the background is grey. The knowledge in those boxes are general facts that are not specific to whatever thing we are implementing. I updated the Textures and Icons section to say that the image can be any scalar of 16x16 pixels where 256x256 will have each icon be 16x16 in size.



Information Icon.png

This page was made with Minecraft Forge Forge Version::11.15.0.1696. It might not work with other versions.


Goals

  • To understand the Forge Plant API
  • To understand how to use metadata to keep track of plant growth.
  • To understand how to make tall grass drop your seed type.

At the end of this tutorial, we will have a clone of wheat, which we will call tomatoes.

Prerequisites

Plant API

{{General Knowledge|Forge has an API for dealing with custom plants. This API adds the EnumPlantType enumeration and the IPlantable interface.

The IPlantable interface is for the class that actually tries to plant the new plant. There are two methods that must be implemented:

<syntaxhighlight lang="java">public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos)</syntaxhighlight>

Determines where the plant can grow. See the next section for more information.

<syntaxhighlight lang="java">public IBlockState getPlant(IBlockAccess world, BlockPos pos)</syntaxhighlight>

Determines the block (and state) of the plant to place.

Where Can The Plant Grow?

The values of EnumPlantType determine where a plant can grow. The restriction is on the block below the plant.

PlantType Restriction Vanilla Plant
Beach Block must be grass, sand, or dirt and next to water. Sugarcane
Cave Top side of block must be solid. Mushroom
Crop Block must be tilled soil. Wheat
Desert Block must be sand. Cactus
Nether Block must be soul sand. Nether Wart
Plains Block must be grass or dirt. Sapling
Water Block must be still water. Lillypad

Seeds

For tomato seeds, we are going to use Minecraft's ItemSeeds directly. It's constructor is as follows:

<syntaxhighlight lang="java">public ItemSeeds(Block crops, Block soil)</syntaxhighlight>

The tomotoSeeds field declaration looks like this. `tomato` will be defined next.

<syntaxhighlight lang="java">public static final Item tomatoSeeds = new ItemSeeds(tomatoCrop, Blocks.farmland) .setUnlocalizedName("seeds.tomato");</syntaxhighlight>

ItemSeeds

So, what is in ItemSeeds? ItemSeeds subclasses Item, overrides onItemUse, and implements IPlantable.

onItemUse

{{{1}}}

For ItemSeeds, onItemUse does the following.

  1. Return false if any of these are true.
    • Side isn't top
    • Player can't edit the block or the block above it
    • Block is not the soil type
    • Block above is not air
  2. Place the plant block on the above air block with metadata = 0
  3. Decrease the size of the item stack by one.

Though it could, this method does not use any of the IPlantable methods.

IPlantable

The IPlantable methods are implemented as follows.

  • getPlantType will return EnumPlantType.crops unless the plant type is nether wart.
  • getPlant will return the BlockState of the block passed in by the constructor.

If your plant has seeds, but is not a crop, you'll want to subclass ItemSeeds.

Dropping in Tall Grass

Having your seed drop in tall grass is exceedingly simple, since it is a forge hook found in net.minecraftforge.common.MinecraftForge. I'd suggest, reading all of the javadocs of that class, since they are well written. Here's the javadoc for addGrassSeed.

<syntaxhighlight lang="java">/**

* Register a new seed to be dropped when breaking tall grass.
*
* @param seed The item to drop as a seed.
* @param weight The relative probability of the seeds,
*               where wheat seeds are 10.
*/

public static void addGrassSeed(ItemStack seed, int weight)</syntaxhighlight>

We will use this hook in our mod class's init method, using the same weight as wheat seeds.

<syntaxhighlight lang="java">MinecraftForge.addGrassSeed(new ItemStack(tomatoSeeds), 10);</syntaxhighlight>

TomatoFruit

For now, the fruit won't do anything. It'll just be an item. Add this field to the base mod class and the next line to the preInit method.

<syntaxhighlight lang="java">public Item tomatoFruit = new Item().setUnlocalizedName("tomatoFruit");</syntaxhighlight>

<syntaxhighlight lang="java">GameRegistry.registerItem(tomatoFruit, "tomatoFruit");</syntaxhighlight>

In the foods and potions tutorial, this will become its own class.

Furthermore, since TomatoCrop won't be dropping seeds when grown, we need a way to keep crops renewable. As such, this recipe will allow the player to trade one tomato for four seeds.

<syntaxhighlight lang="java">GameRegistry.addShapelessRecipe(new ItemStack(tomatoSeeds, 4), new ItemStack(tomatoFruit));</syntaxhighlight>

TomatoCrop

Block Registration

Register the block normally, with the caveat that the field is listed before the tomato seeds, since the tomato seeds depends on the block.

There are two things to note about this block registration.

  1. Register the field before you register the tomatoSeeds field, since tomatoSeeds depends on tomatoCrop.
  2. Since the player should never hold tomatoCrop in their inventory, you do not need to give a public facing name to it.

<syntaxhighlight lang="java">public static final Block tomatoCrop = new TomatoCrop();</syntaxhighlight>

<syntaxhighlight lang="java">GameRegistry.registerBlock(tomatoCrop, "tomatoCrop");</syntaxhighlight>

Rendering

Unfortunately, rendering is a black art to me. I literally pulled everything that had to do with rendering from Wheat's code.

The block texture (via GitHub) are thanks to NeverCast.

Here's Tomato with the rendering code.

<syntaxhighlight lang="java">package tutorial.generic;

import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.BlockPos; import net.minecraft.world.World;

public class TomatoCrop extends GenericBlock {

   public TomatoCrop() {
       super(Material.plants);
       this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F , 1.5F, 1.0F);
   }
   public AxisAlignedBB getCollisionBoundingBoxFromPool(World world, BlockPos pos, IBlockState state) {
       return null;
   }
   public int getRenderType() {
       return 6; // Magic number.
   }
   
   public boolean isOpaqueCube() {
       return false;
   }

}</syntaxhighlight>

Metadata

{{{1}}}

For tomatoes, we are going to use only two values, 0 and 1. The value 0 will be used for ungrown tomatoes while 1 will be used for grown, ready to harvest tomatoes.

Texture

The texture differs, based on whether the plant is grown or not. In the texture file, position 32 contains the ungrown tomato while position 33 contains the grown position. When your texture and metadata values correspond on a one-for-one value, it makes sense to order the textures by metadata value, so that you can override getBlockTextureFromSideAndMetadata() to return the metadata 0 position plus metadata. For tomatoes, it looks like this:

<syntaxhighlight lang="java">@Override public int getBlockTextureFromSideAndMetadata (int side, int metadata) {

   return 32 + metadata;

}</syntaxhighlight>

Growing (Ticks)

{{{1}}}

This is our algorithm for updateTick for tomatoes.

  1. If metadata is grown (1), return. It's fully grown, and will stay in that state.
  2. If the light level above us is less than 9, return.
  3. If the tilled soil is wet, growth rate is 12. Otherwise, it is 25.
  4. If a random int in the range [0 to growth rate) is not 0. return.
  5. Set the metadata to grown (1).

The first step makes sure not to run any expensive processing if the plant cannot grow. This check is cheap compared to the rest, so make sure to have it first.

The second step is to make sure that we don't grow at night time or placed in too shady of an area. We check the block above us instead of our block to simulate light rays going down having the strongest effect.

The third and fourth steps slow the growth rate down. While the random ticks don't happen every tick, they happen frequently enough that we want to slow it down. Since we cannot change the rate at which random ticks occur, we just add another random wait process. If you want to mess with the time it takes to grow based on world factors, these are the numbers you change. Wheat has quite a complicated growing algorithm that makes it favour rows over

The final step is to grow the plant.

Here is the implementation. The first line is added to the constructor.

<syntaxhighlight lang="java">setTickRandomly(true)</syntaxhighlight>

<syntaxhighlight lang="java">@Override public void updateTick(World world, int x, int y, int z, Random random) {

   if (world.getBlockMetadata(x, y, z) == 1) {
       return;
   }
   if (world.getBlockLightValue(x, y + 1, z) < 9) {
       return;
   }
   if (random.nextInt(isFertile(world, x, y - 1, z) ? 12 : 25) != 0) {
       return;
   }
   world.setBlockMetadata(x, y, z, 1);

}</syntaxhighlight>

Automatic Breaking

Plants don't survive in dark places, when hit by moving liquids, and if they aren't in soil.

Something in the rendering code already makes the block vulnerable to moving liquids, so no new code is needed there. Otherwise, we are going to be using the following methods:

{{{1}}}

Our implementation is to see if the plant can stay every time its neighbor changes. If it cannot, then simulate the block breaking with dropBlockAsItem and world.setBlockWithNotify.

<syntaxhighlight lang="java">@Override public void onNeighborBlockChange (World world, int x, int y, int z,

       int neighborId) {
   if (!canBlockStay(world, x, y, z)) {
       dropBlockAsItem(world, x, y, z, world.getBlockMetadata(x, y, z), 0);
       world.setBlockWithNotify(x, y, z, 0);
   }

}</syntaxhighlight>

For seeing if the plant can stay, we first ask what block is below us. We then query two things.

  1. If the plant is in a bright enough area or can see the sky.
  2. If the block below it can sustain it.

If either is false, we return false, since those are the necessary conditions to be able to grow.

<syntaxhighlight lang="java">@Override public boolean canBlockStay (World world, int x, int y, int z) {

   Block soil = blocksList[world.getBlockId(x, y - 1, z)];
   return (world.getFullBlockLightValue(x, y, z) >= 8 || 
           world.canBlockSeeTheSky(x, y, z)) &&
           (soil != null && soil.canSustainPlant(world, x, y - 1, z,
                 ForgeDirection.UP, Generic.tomatoSeeds));

}</syntaxhighlight>

This algorithm has a bug in it where there are conditions where canBlockStay will return false but onBlockNeighborChange is not called. Mushrooms also have this bug. Specifically, if you build a dark house around the plant without touching the plant, it won't break. It won't grow either though. Mushrooms have the opposite problem where if you let in light by breaking a wall that blocks light 2 blocks away from the mushroom, it won't uproot. If a block next to the plant/mushroom does change though, it will then break of course.

Dropping Tomatoes

Dropping the fruit is just like dropping ingots. The only difference is, is that we need to drop the seeds if the crop isn't grown and the fruit if they are grown. The metadata value is passed along, so we can return the proper item based on what it is.

<syntaxhighlight lang="java">@Override public int idDropped(int metadata, Random random, int par2) {

   switch (metadata) {
   case 0:
       return Generic.tomatoSeeds.itemID;
   case 1:
       return Generic.tomatoFruit.itemID;
   default: // Error case!
       return -1; // no item
   }

}</syntaxhighlight>

I decided to go with a switch here just to allow for the possible error case. I would also log this error and possibly throw an error after testing to see that throwing an error there wouldn't crash Minecraft.

Picking tomatoes

{{{1}}}

Picking wheat in this manner gives you wheat seeds. Let's follow this behaviour for tomatoes. That way the player cannot place the tomatoCrop or ever carry it in their inventory. If they could, they could place the crop anywhere! Here's our implementation.

<syntaxhighlight lang="java">@Override public int idPicked (World world, int x, int y, int z) {

   return Generic.tomatoSeeds.itemID;

}</syntaxhighlight>

Bonemeal

I'm going to punt on explaining Bonemeal since there is another tutorial on the wiki called Bonemeal For Forge 1.3.2 and it requires explaining events first.

Finishing Up

While this tutorial is only a wheat clone, the code is similar for all plants. The only difference is, is that crops that grow off of themselves will need to implement IPlantable themselves instead of letting some item be the plantable thing.

Generic Class

<syntaxhighlight lang="java">package tutorial.generic;

// This Import list will grow longer with each additional tutorial. // It's not pruned between full class postings, unlike other tutorial code. import net.minecraft.block.Block; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.Item; import net.minecraft.item.ItemSeeds; import net.minecraft.item.ItemStack; import net.minecraft.block.material.Material; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.MinecraftForge; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.Init; import cpw.mods.fml.common.Mod.Instance; import cpw.mods.fml.common.Mod.PostInit; import cpw.mods.fml.common.Mod.PreInit; import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.network.NetworkMod; import cpw.mods.fml.common.registry.GameRegistry; import cpw.mods.fml.common.registry.LanguageRegistry;

@Mod(modid = "Generic", name = "Generic", version = "0.0.0") @NetworkMod(clientSideRequired = true, serverSideRequired = false, channels = { "GenericRandom" }, packetHandler = PacketHandler.class) public class Generic {

   public static final Block tomatoCrop = new TomatoCrop(504);
   public static final ItemSeeds tomatoSeeds = (ItemSeeds) new ItemSeeds(5002,
           tomatoCrop.blockID, Block.tilledField.blockID).setIconIndex(2)
           .setTextureFile(CommonProxy.ITEMS_PNG);
   public static final Item tomatoFruit = new GenericItem(5002);
   @Instance("Generic")
   public static Generic instance;
   @SidedProxy(clientSide = "tutorial.generic.client.ClientProxy", serverSide = "tutorial.generic.CommonProxy")
   public static CommonProxy proxy;
   @PreInit
   public void preInit (FMLPreInitializationEvent event) {
       // Stub Method
   }
   @Init
   public void load (FMLInitializationEvent event) {
       proxy.registerRenderers();
       LanguageRegistry.addName(tomatoSeeds, "Tomato Seeds");
       MinecraftForge.addGrassSeed(new ItemStack(tomatoSeeds), 10);
       LanguageRegistry.addName(tomatoFruit, "Tomato");
       GameRegistry.addShapelessRecipe(new ItemStack(tomatoSeeds, 4),
               new ItemStack(tomatoFruit));
       GameRegistry.registerBlock(tomatoCrop, "tomatoCrop");
   }
   @PostInit
   public void postInit (FMLPostInitializationEvent event) {
       // Stub Method
   }

}</syntaxhighlight>

TomatoCrop Class

<syntaxhighlight lang="java">package tutorial.generic;

import java.util.Random;

import net.minecraft.block.material.Material; import net.minecraft.block.Block; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.World; import net.minecraftforge.common.ForgeDirection;

public class TomatoCrop extends GenericBlock {

   public TomatoCrop (int id) {
       super(id, 32, Material.plants);
       setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.5F, 1.0F);
       setTickRandomly(true);
   }
   @Override
   public AxisAlignedBB getCollisionBoundingBoxFromPool (World world, int x,
           int y, int z) {
       return null;
   }
   @Override
   public int getRenderType () {
       return 6;
   }
   @Override
   public boolean isOpaqueCube () {
       return false;
   }
   @Override
   public int getBlockTextureFromSideAndMetadata (int side, int metadata) {
       return 32 + metadata;
   }
   @Override
   public void updateTick (World world, int x, int y, int z, Random random) {
       if (world.getBlockMetadata(x, y, z) == 1) {
           return;
       }
       if (random.nextInt(isFertile(world, x, y - 1, z) ? 12 : 25) != 0) {
           return;
       }
       world.setBlockMetadataWithNotify(x, y, z, 1);
   }
   @Override
   public void onNeighborBlockChange (World world, int x, int y, int z,
           int neighborId) {
       if (!canBlockStay(world, x, y, z)) {
           dropBlockAsItem(world, x, y, z, world.getBlockMetadata(x, y, z), 0);
           world.setBlockWithNotify(x, y, z, 0);
       }
   }
   @Override
   public boolean canBlockStay (World world, int x, int y, int z) {
       Block soil = blocksList[world.getBlockId(x, y - 1, z)];
       return (world.getFullBlockLightValue(x, y, z) >= 8 || world
               .canBlockSeeTheSky(x, y, z))
               && (soil != null && soil.canSustainPlant(world, x, y - 1, z,
                       ForgeDirection.UP, Generic.tomatoSeeds));
   }
   @Override
   public int idDropped (int metadata, Random random, int par2) {
       switch (metadata) {
       case 0:
           return Generic.tomatoSeeds.itemID;
       case 1:
           return Generic.tomatoFruit.itemID;
       default:
           // Error case!
           return -1; // air
       }
   }
   @Override
   public int idPicked (World world, int x, int y, int z) {
       return Generic.tomatoSeeds.itemID;
   }

}</syntaxhighlight>

What's Next?

Currently this is the end of this path. Go back to the table of contents for more tutorials.