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.



Contents

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

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 three methods that must be implemented:

public EnumPlantType getPlantType(World world, int x, int y, int z)

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

public int getPlantID(World world, int x, int y, int z)

Determines the block ID of the plant to place.

public int getPlantMetadata(World world, int x, int y, int z)

Determines the initial metadata value of the plant.

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

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be grass, sand, or
dirt and next to water.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Sugarcane
Cave

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Top side of block must be solid.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Mushroom
Crop

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be tilled soil.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Wheat
Desert

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be sand.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Cactus
Nether

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be soul sand.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Nether Wart
Plains

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be grass or dirt.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Sapling
Water

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Block must be still water.

Warning Icon.png

This page is marked for Deletion. It is no longer relevant, needed, or is a result of an error or issue. Only certain users with appropriate rights will be able to delete this page.

Lillypad

Seeds

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

public ItemSeeds(int id, int plantId, int soilId)

The only change we need to make is to change the texture file for the seeds, but instead of subclassing, we will call setTextureFile. Since setTextureFile doesn't return the Item back, it will have to called in the Init block. We'll also have to call setIconIndex and setItemName, which will be chained on the constructor.

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

public static final ItemSeeds tomatoSeeds = new ItemSeeds(5002, tomatoCrop.blockID, Block.tilledField.blockID)
.setIconIndex(2)
.setItemName("seeds.tomato");

The following lines are addeded to the mod initialization method.

tomatoSeeds.setTextureFile(CommonProxy.BLOCK_PNG);
LanguageRegistry.addName(tomatoSeeds, "Tomato Seeds");

ItemSeeds

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

onItemUse

When a player right clicks with an item, onItemUse is called. It has the following signature.
public boolean onItemUse(ItemStack itemStack, EntityPlayer player, World world,
    int bx, int by, int bz, int side, float px, float py, float pz)
The first coordinates are the coordinates of the block the cursor is over. Side is the side of the block the cursor is over. The second set of coordinates are the exact coordinates of where the user's cursor was pointing at. The return value is whether or not something happened.

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.
  • getPlantID will return the plantID passed in by the constructor.
  • getPlantMetadta returns zero.

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.

/**
 * 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)

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

MinecraftForge.addGrassSeed(new ItemStack(tomatoSeeds), 10);

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 init method.

public Item tomatoFruit = new ForgeItem(5003);
LanguageRegistry.addName(tomatoFruit, "Tomato");

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.

GameRegistry.addShapelessRecipe(new ItemStack(tomatoSeeds, 4), new ItemStack(tomatoFruit));

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.
public static final Block tomatoCrop = new TomatoCrop(504);
GameRegistry.registerBlock(tomatoCrop, "tomatoCrop");

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.

package tutorial.generic;

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

public class TomatoCrop extends GenericBlock {

    public TomatoCrop(int id) {
        super(id, 32, Material.plants);
        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F , 1.5F, 1.0F);
    }
       
    public AxisAlignedBB getCollisionBoundingBoxFromPool(World world, int x, int y, int z) {
        return null;
    }
       
    public int getRenderType() {
        return 6; // Magic number.
    }
   
    public boolean isOpaqueCube() {
        return false;
    }
}

Metadata

Blocks are made up of two parts: A block id and metadata. The block id determines which class to call methods on. The metadata allows the class to differentiate logic even further. While a block id can go up to 4095 (12 bits), metadata can only go up to 15 (4 bits).

A lot of block methods either pass the metadata value of the block or the position (x, y, z, and world) of the block. Given the position, you can get a block's metadata by using the following method on World.

public int getBlockMetadata(int x, int y, int z)
Just like damage values for items, metadata for blocks have no prescribed information, and can be used arbitrarily by each block.

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:

@Override
public int getBlockTextureFromSideAndMetadata (int side, int metadata) {
    return 32 + metadata;
}

Growing (Ticks)

Minecraft updates 20 times per second, at best. This update is known as a tick, which loaded blocks and entities respond to. By default, blocks do nothing during a tick, but by overriding updateTick, a block can have its own behavior.

updateTick has the following signature.

public void updateTick(World world, int x, int y, int z, Random random)

Unless your block actually does something every tick, the block really should not update every tick. Instead, the block should be configured to update every random amount of ticks. This is done in the constructor. The method for doing so has the following signature.

public void setTickRandomly(boolean tickRandomly);

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.

setTickRandomly(true)
@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);
}

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:

public void onNeighborBlockChange (World world, int x, int y, int z, int neighborId)

Part of Block, whenever there is a notified change, it calls this method on all of the surrounding blocks. As such, when a block changes next to the block that implements this method, this code will run. The passed location is of the implementing block, not the block which changed. By default, this method does nothing.

public boolean canBlockStay (World world, int x, int y, int z);

Also part of Block. By default, it returns true. Other than being called in onNeighborBlockChange methods, it is also called often in world gen of plants.

public int getFullBlockLightValue(int x, int y, int z);

A World method, its name gives its function. It returns the light value between 0 for absolute darkness and 15 for brightest light possible in Minecraft.

public void setBlockWithNotify(int x, int y, int z, int id);

Another World method. This one completely replaces the block at the specified location with one of the given id and a metadata value of zero.

public int dropBlockAsItem(World world, int x, int y, int z, int metadata, int fortune);
This method in Block is called when we want to drop the block's items. Very few blocks override the default implementation, which is quite complicated, but easy to understand. Usually, we don't call this method, but when we simulate a block breaking, we have to call it. Make sure that you break the block in a way that doesn't cause it to drop itself again afterwards. The World.setBlock family of methods are good candidates for this.

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.

@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);
    }
}

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.

@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));
}

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.

@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
    }
}

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

When in creative mode, players can pick blocks with the middle mouse button (button configurable in settings). Instead of receiving the block clicked, we can give them back any arbitrary block or item with idPicked.
public int idPicked (World world, int x, int y, int z);
The int it returns is the item id of the item to give. It's default value is to give the block clicked back.

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.

@Override
public int idPicked (World world, int x, int y, int z) {
    return Generic.tomatoSeeds.itemID;
}

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

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
    }
}

TomatoCrop Class

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;
    }
}

What's Next?

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

Personal tools
Namespaces
Variants
Actions
Navigation
tutorials
Toolbox