Because we all want to make Minecraft a little better
These are my tutorials, written with a serious modder in mind. Anyone at all can use them though. At each point I explain some of the reasoning behind what each piece of the puzzle does and why I code the way I do. There's a nice chunk of meat in each tutorial; don't be afraid to download the examples and rip it apart instead.
Note: Anything Forge specific has a [Forge] tag. Everything else applies to anyone.
Learning to create a Minecraft mod means learning to program. You won't get anywhere without a modicum of skill and a good Java tutorial at hand. There's a wide variety of material out there; I've grabbed a few more interesting ones. Give the design ones a lookover and then dive right into the java tutorial before you start to do ANY modding. Trust me, you'll wish you had.
The first thing you'll need to do is set up a mod_ file. These are the files that ModLoader reads, so it will be the starting point to load up your code. Create a new file named "mod_HelloWorld.java" in your source directory.
The next step is to decide what you actually want to do. For this example I decided I wanted to create a couple ores and a pickaxe. There's a lot going on in here, so try to keep up.
/* The physical location of the file. */
package net.minecraft.src;
/* We need to import any files that are outside the package.
* Importing a whole folder is also valid:
* import net.minecraft.src.forge.*;
*/
import java.util.Random;
/* This mod inherits some properties from BaseModMp */
public class mod_HelloWorld extends BaseModMp
{
/* Default constructor for the mod.
* Nothing special yet, so nothing in here
*/
public mod_HelloWorld() {}
}
This is a bare bones mod_ file. It needs a couple method for it to do anything, or the compiler will spew errors at you.
@Override
public String getVersion()
{/* This string can be anything you want */
return "Hello World";
}
/* Load runs before the class is initialized.
* Its main uses are for grabbing property files,
* registering blocks, and adding recipes.
*/
@Override
public void load()
{
}
Now the fun can really begin. All tools begin with a block, so we'll need to add one in.
/* Prototype fields, used elsewhere */
public static Block copperMultiOre;
This piece of code can go anywhere in the file. It's an empty Block (aka prototype); we'll need to fill it in. I like to put my prototypes right above the static block:
public static Block copperMultiOre;
static
{
copperMultiOre = new CopperOre(PropsHelperHelloWorld.copperOreID, 0)
.setBlockName("Copper Multi Ore");
}
Ignore the props helper for now. The block is a new CopperOre... err, where's the copper ore?
What you need to do now is to create a new folder in your source directory. Call it "helloworld" for good measure, and create a new file in that called "CopperOre.java". Because I wanted a few ores, but only wanted to use one block ID, my copper block ended up looking like this:
/* Note that the package is different from the mod_ class
* because this file is in a folder
*/
package net.minecraft.src.helloworld;
import net.minecraft.src.forge.ITextureProvider;
import net.minecraft.src.Block;
import net.minecraft.src.Material;
/* [Forge] Custom sprite sheets for blocks */
public class CopperOre extends Block
implements ITextureProvider
{
public CopperOre(int itemID, int texture)
{
super(itemID, texture, Material.rock);
}
/* Used for dropping the proper block metadata on harvest
* Metadata runs from 0-15, meaning 16 possible
* values per block.
* Every block has metadata built in, so use it where possible.
*/
@Override
protected int damageDropped(int metadata)
{
return metadata;
}
/* This method determines what textures show up on the block
* The block will show different textures on the side for
* the first two metadata, and the same texture for the third
*/
@Override
public int getBlockTextureFromSideAndMetadata(int side, int metadata)
{
if (metadata == 0 || metadata == 1)
{
if (side != 0 && side != 1)
{
return blockIndexInTexture + side + metadata * 16;
}
else
{
return blockIndexInTexture + metadata;
}
}
else
{
return blockIndexInTexture + metadata;
}
}
/* This is the location of the file that will show
* this block's textures
* @see net.minecraft.src.forge.ITextureProvider#getTextureFile()
*/
@Override
public String getTextureFile()
{
return "/helloworldtextures/textures.png";
}
}
Every block has metadata, whether it likes it or not. The key is to have an itemblock for each block that exists. Create a new file named "CopperOreItem.java" in helloworld and Observe:
package net.minecraft.src.helloworld;
import net.minecraft.src.ItemBlock;
import net.minecraft.src.ItemStack;
public class CopperOreItem extends ItemBlock
{
public CopperOreItem(int i)
{
super(i);
setMaxDamage(0);
setHasSubtypes(true);
}
/* This method determines what metadata
* is placed when using the block.
* Override it to represent
*/
@Override
public int getMetadata(int metadata)
{
return metadata;
}
/* Set up a simple naming system */
public static final String blockType[] =
{
"Copper", "Turquoise", "Chalcocite"
};
/* This method determines what name is displayed on the screen.
* Use a StringBuilder here for maximum fps
*/
@Override
public String getItemNameIS(ItemStack itemstack)
{
return new StringBuilder().append("ore").append(blockType[itemstack.getItemDamage()]).toString();
}
}
Next, let's add a pickaxe. This one will be simple; it will just be a normal pickaxe that uses a custom texture file.
package net.minecraft.src.helloworld;
import net.minecraft.src.ItemPickaxe;
import net.minecraft.src.Material;
import net.minecraft.src.EnumToolMaterial;
import net.minecraft.src.forge.ITextureProvider;
/* [Forge] Custom sprite sheets for items */
public class CopperPickaxe extends ItemPickaxe
implements ITextureProvider
{
public CopperPickaxe(int itemID, EnumToolMaterial mat)
{
super(itemID, mat);
}
/* This is the location of the file that will show
* this item's textures
* @see net.minecraft.src.forge.ITextureProvider#getTextureFile()
*/
@Override
public String getTextureFile()
{
return "/helloworldtextures/textures.png";
}
}
The files are ready to use. They don't actually do anything right now, so we need to let Minecraft know that they exist. We also need to define what exactly the pickaxe is, so let's add a few bits of code relating to that as well.
@Override
public void load()
{
/* Break up methods to keep code cleaner */
this.registerBlocks();
this.addNames();
}
private void registerBlocks()
{
/* Ties a particular block to an itemblock */
ModLoader.registerBlock(copperMultiOre, net.minecraft.src.helloworld.CopperOreItem.class);
}
private void addNames()
{
/* Add names for ores using a special ModLoader method */
ModLoader.addLocalization("oreCopper.name", "Copper Ore");
ModLoader.addLocalization("oreTurquoise.name", "Turquoise Ore");
ModLoader.addLocalization("oreChalcocite.name", "Chalcocite Ore");
}
/* Prototype fields, used elsewhere */
public static Block copperMultiOre;
public static Item copperPickaxe;
public static Item copperIngot;
/* [Forge] Used to define a custom tool material */
static EnumToolMaterial materialCopper = EnumHelper.addToolMaterial("COPPER", 1, 180, 5.0F, 1, 7);
/* Static block. Useful for defining fields
* or methods that will never change.
*/
static
{
copperMultiOre = new CopperOre(PropsHelperHelloWorld.copperOreID, 0)
.setBlockName("Copper Multi Ore");
copperPickaxe = new CopperPickaxe(PropsHelperHelloWorld.copperPickaxeID, materialCopper)
.setIconCoord(2, 0).setItemName("Copper Pickaxe");
copperIngot = new TexturedItem(PropsHelperHelloWorld.copperIngotID, "/helloworldtextures/textures.png")
.setIconCoord(2, 0).setItemName("Copper Ingot");
}
This looks good and all... if you ignore the props helper. Let's not ignore it anymore; add a file named "PropsHelperHelloWorld.java" inside the helloworld folder. Try to digest as much of this information as possible; you'll need it later.
package net.minecraft.src.helloworld;
import java.io.File;
import java.io.IOException;
import net.minecraft.client.Minecraft;
import net.minecraft.src.forge.Configuration;
public class PropsHelperHelloWorld {
public static void initProps()
{
/* Here we will set up the config file for the mod
* First: Create a folder inside the config folder
* Second: Create the actual config file
* Note: Configs are a pain, but absolutely necessary for every mod.
*/
File file = new File(Minecraft.getMinecraftDir() + "/config/HelloWorld");
file.mkdir();
File newFile = new File(Minecraft.getMinecraftDir() + "/config/HelloWorld/HelloWorld.cfg");
/* Some basic debugging will go a long way */
try
{
newFile.createNewFile();
System.out.println("Successfully created/read configuration file");
}
catch (IOException e)
{
System.out.println("Could not create configuration file for mod_HelloWorld. Reason:");
System.out.println(e);
}
/* [Forge] Configuration class, used as config method */
Configuration config = new Configuration(newFile);
/* Load the configuration file */
config.load();
/* Define the mod's IDs.
* Avoid values below 4096 for items and in the 250-600 range for blocks
*/
copperOreID = config.getOrCreateBlockIdProperty("Copper Block", 130).getInt(130);
copperPickaxeID = config.getOrCreateIntProperty("Copper Pickaxe", "item", 4900).getInt(4900);
copperIngotID = config.getOrCreateIntProperty("Copper Ingot", "item", 4901).getInt(4901);
/* Save the configuration file */
config.save();
}
/* Prototype fields, used elsewhere */
public static int copperOreID;
public static int copperPickaxeID;
public static int copperIngotID;
}
Ore needs to generate in the world, right? Right. I copied WorldGenMinable and added a value for metadata:
package net.minecraft.src.helloworld;
import net.minecraft.src.*;
import java.util.Random;
/* This class is a copy of WorldGenMinable.
* It has an added value for metadata sensitivity
*/
public class OreGenerator extends WorldGenerator
{
private int bID;
private int meta;
private int size;
public OreGenerator(int blockID, int metadata, int sizeOfVein)
{
bID = blockID;
meta = metadata;
size = sizeOfVein;
}
public boolean generate(World world, Random random, int i, int j, int k)
{
float f = random.nextFloat() * 3.141593F;
double d = (float)(i + 8) + (MathHelper.sin(f) * (float)size) / 8F;
double d1 = (float)(i + 8) - (MathHelper.sin(f) * (float)size) / 8F;
double d2 = (float)(k + 8) + (MathHelper.cos(f) * (float)size) / 8F;
double d3 = (float)(k + 8) - (MathHelper.cos(f) * (float)size) / 8F;
double d4 = (j + random.nextInt(3)) - 2;
double d5 = (j + random.nextInt(3)) - 2;
for (int l = 0; l <= size; l++)
{
double d6 = d + ((d1 - d) * (double)l) / (double)size;
double d7 = d4 + ((d5 - d4) * (double)l) / (double)size;
double d8 = d2 + ((d3 - d2) * (double)l) / (double)size;
double d9 = (random.nextDouble() * (double)size) / 16D;
double d10 = (double)(MathHelper.sin(((float)l * 3.141593F) / (float)size) + 1.0F) * d9 + 1.0D;
double d11 = (double)(MathHelper.sin(((float)l * 3.141593F) / (float)size) + 1.0F) * d9 + 1.0D;
int i1 = MathHelper.floor_double(d6 - d10 / 2D);
int j1 = MathHelper.floor_double(d7 - d11 / 2D);
int k1 = MathHelper.floor_double(d8 - d10 / 2D);
int l1 = MathHelper.floor_double(d6 + d10 / 2D);
int i2 = MathHelper.floor_double(d7 + d11 / 2D);
int j2 = MathHelper.floor_double(d8 + d10 / 2D);
for (int xGen = i1; xGen <= l1; xGen++)
{
double d12 = (((double)xGen + 0.5D) - d6) / (d10 / 2D);
if (d12 * d12 >= 1.0D)
{
continue;
}
for (int yGen = j1; yGen <= i2; yGen++)
{
double d13 = (((double)yGen + 0.5D) - d7) / (d11 / 2D);
if (d12 * d12 + d13 * d13 >= 1.0D)
{
continue;
}
for (int zGen = k1; zGen <= j2; zGen++)
{
double d14 = (((double)zGen + 0.5D) - d8) / (d10 / 2D);
if (d12 * d12 + d13 * d13 + d14 * d14 < 1.0D &&
world.getBlockId(xGen, yGen, zGen) == Block.stone.blockID)
{
world.setBlockAndMetadata(xGen, yGen, zGen, bID, meta);
}
}
}
}
}
return true;
}
}
And then added the generators to the mod class.
/* Set up generation of ores in the world.
* Modularize the method for more efficient coding
* and cleaner code.
*/
@Override
public void generateSurface(World world, Random random, int chunkX, int chunkZ)
{
generateVeins(world, random, chunkX, chunkZ, copperGen, 10, 64);
generateVeins(world, random, chunkX, chunkZ, turquoiseGen, 10, 64);
generateVeins(world, random, chunkX, chunkZ, chalcociteGen, 10, 64);
}
public static boolean generateVeins(World world, Random random, int chunkX, int chunkZ,
OreGenerator oregenerator, int rarity, int height)
{
/* Programming artifact: Array indexes start at 0 */
for (int i = 0; i < rarity; i++)
{
int hi = random.nextInt(height);
int randX = chunkX + random.nextInt(16);
int randZ = chunkZ + random.nextInt(16);
oregenerator.generate(world, random, randX, hi, randZ);
}
return true;
}
public static OreGenerator copperGen;
public static OreGenerator turquoiseGen;
public static OreGenerator chalcociteGen;
static
{
/* Values for Ore Generator: Block ID, Metadata, Vein Size */
copperGen = new OreGenerator(copperMultiOre.blockID, 0, 20);
turquoiseGen = new OreGenerator(copperMultiOre.blockID, 1, 12);
chalcociteGen = new OreGenerator(copperMultiOre.blockID, 2, 6);
}
The only thing left to do is add some recipes for the items, test, and call it good.
@Override
public void load()
{
/* Break up methods to keep code cleaner */
this.registerBlocks();
this.addNames();
this.addRecipes();
}
private void addRecipes()
{
/* Here we add custom crafting and smelting recipes */
CraftingManager.getInstance().addRecipe(new ItemStack(this.copperPickaxe, 1), new Object[]
{ "###", " | ", " | ", '#', this.copperIngot, '|', Item.stick });
/* [Forge] Metadata smelting recipes
* Values: Item ID, Metadata, Itemstack for output */
FurnaceRecipes.smelting().addSmelting(this.copperMultiOre.blockID,
0, new ItemStack(this.copperIngot, 1));
}
The mod_ class ended up looking like this:
/* The physical location of the file. */
package net.minecraft.src;
/* We need to import any files that are outside the package.
* Importing a whole folder is also valid:
* import net.minecraft.src.forge.*;
*/
import java.util.Random;
import net.minecraft.client.Minecraft;
import net.minecraft.src.helloworld.*;
import net.minecraft.src.forge.EnumHelper;
/* This mod inherits some properties from BaseModMp */
public class mod_HelloWorld extends BaseModMp
{
/* Default constructor for the mod.
* Nothing special yet, so nothing in here
*/
public mod_HelloWorld() {}
/* @Override is a way of telling your compiler
* that there's a method in the parent class
* that needs different behavior in this class.
* Use it often.
*/
@Override
public String getVersion()
{/* This string can be anything you want */
return "Hello World";
}
/* Load runs before the class is initialized.
* Its main uses are for grabbing property files,
* registering blocks, and adding recipes.
*/
@Override
public void load()
{
/* Break up methods to keep code cleaner */
this.registerBlocks();
this.addNames();
this.addRecipes();
}
private void registerBlocks()
{
/* Ties a particular block to an itemblock */
ModLoader.registerBlock(copperMultiOre, net.minecraft.src.helloworld.CopperOreItem.class);
}
private void addNames()
{
/* Add names for ores using a special ModLoader method */
ModLoader.addLocalization("oreCopper.name", "Copper Ore");
ModLoader.addLocalization("oreTurquoise.name", "Turquoise Ore");
ModLoader.addLocalization("oreChalcocite.name", "Chalcocite Ore");
/* Add pickaxe name using the standard method */
ModLoader.addName(copperPickaxe, "Copper Pickaxe");
}
private void addRecipes()
{
/* Here we add custom crafting and smelting recipes */
CraftingManager.getInstance().addRecipe(new ItemStack(this.copperPickaxe, 1), new Object[]
{ "###", " | ", " | ", '#', this.copperIngot, '|', Item.stick });
/* [Forge] Metadata smelting recipes
* Values: Item ID, Metadata, Itemstack for output */
FurnaceRecipes.smelting().addSmelting(this.copperMultiOre.blockID,
0, new ItemStack(this.copperIngot, 1));
}
/* Set up generation of ores in the world.
* Modularize the method for more efficient coding
* and cleaner code.
*/
@Override
public void generateSurface(World world, Random random, int chunkX, int chunkZ)
{
generateVeins(world, random, chunkX, chunkZ, copperGen, 10, 64);
generateVeins(world, random, chunkX, chunkZ, turquoiseGen, 10, 64);
generateVeins(world, random, chunkX, chunkZ, chalcociteGen, 10, 64);
}
public static boolean generateVeins(World world, Random random, int chunkX, int chunkZ,
OreGenerator oregenerator, int rarity, int height)
{
/* Programming artifact: Array indexes start at 0 */
for (int i = 0; i < rarity; i++)
{
int hi = random.nextInt(height);
int randX = chunkX + random.nextInt(16);
int randZ = chunkZ + random.nextInt(16);
oregenerator.generate(world, random, randX, hi, randZ);
}
return true;
}
/* Prototype fields, used elsewhere */
public static Block copperMultiOre;
public static Item copperPickaxe;
public static Item copperIngot;
public static OreGenerator copperGen;
public static OreGenerator turquoiseGen;
public static OreGenerator chalcociteGen;
/* [Forge] Used to define a custom tool material */
static EnumToolMaterial materialCopper = EnumHelper.addToolMaterial("COPPER", 1, 180, 5.0F, 1, 7);
/* Static block. Useful for defining fields
* or methods that will never change.
*/
static
{
PropsHelperHelloWorld.initProps();
copperMultiOre = new CopperOre(PropsHelperHelloWorld.copperOreID, 0)
.setBlockName("Copper Multi Ore");
copperPickaxe = new CopperPickaxe(PropsHelperHelloWorld.copperPickaxeID, materialCopper)
.setIconCoord(2, 0).setItemName("Copper Pickaxe");
copperIngot = new TexturedItem(PropsHelperHelloWorld.copperIngotID, "/helloworldtextures/textures.png")
.setIconCoord(2, 0).setItemName("Copper Ingot");
/* Values for Ore Generator: Block ID, Metadata, Vein Size */
copperGen = new OreGenerator(copperMultiOre.blockID, 0, 20);
turquoiseGen = new OreGenerator(copperMultiOre.blockID, 1, 12);
chalcociteGen = new OreGenerator(copperMultiOre.blockID, 2, 6);
}
}
Lesson 1 is more of "Let's get something working" than "Let's talk about each thing in detail". Besides, modding is about exploration and trying things for yourself, not shoving a bland ore down someone's throat. Lesson 2 will be all about metadata and what you can do with it.
Really nice of you about writing this with forge + config/folder/metadata, very helpful the first lesson I find.
Except that you seem to have forgotten to write in-game name for 'Copper Ingot', and
.setHardness(*F)
for the ores(breaking speed).
I would love to see tutorials for ex. about items/food w/special effects/abilities, also armor.
P.S. Is there any way to make blocks drop entities on braking? Like Primed TNT, or XP
Do you think you could make a tut. for setting up MCP? I used to be able to make simple mods but then my comp. exploded and I got a new one... ANYWAY, I need to get MCP set up again and i cant figure it out. :/
These are my tutorials, written with a serious modder in mind. Anyone at all can use them though. At each point I explain some of the reasoning behind what each piece of the puzzle does and why I code the way I do. There's a nice chunk of meat in each tutorial; don't be afraid to download the examples and rip it apart instead.
Note: Anything Forge specific has a [Forge] tag. Everything else applies to anyone.
Lesson 1: Hello Minecraft World! - Blocks, Block Metadata, Tools, Custom Textures, Configuration Files, World Generation
Sadly, I've lost interest in making tutorials in favor of modding. Feel free to use this as a decent beginner's guide.
Learning to create a Minecraft mod means learning to program. You won't get anywhere without a modicum of skill and a good Java tutorial at hand. There's a wide variety of material out there; I've grabbed a few more interesting ones. Give the design ones a lookover and then dive right into the java tutorial before you start to do ANY modding. Trust me, you'll wish you had.
Good Code Design
General Code Design Qualities
Java Tutorial for New Programmers
Java Tutorial from Oracle
This tutorial covers the absolute basics. Example Link
Inside: Blocks, Block Metadata, Tools, Custom Textures, Configuration Files, World Generation
The first thing you'll need to do is set up a mod_ file. These are the files that ModLoader reads, so it will be the starting point to load up your code. Create a new file named "mod_HelloWorld.java" in your source directory.
The next step is to decide what you actually want to do. For this example I decided I wanted to create a couple ores and a pickaxe. There's a lot going on in here, so try to keep up.
This is a bare bones mod_ file. It needs a couple method for it to do anything, or the compiler will spew errors at you.
Now the fun can really begin. All tools begin with a block, so we'll need to add one in.
This piece of code can go anywhere in the file. It's an empty Block (aka prototype); we'll need to fill it in. I like to put my prototypes right above the static block:
Ignore the props helper for now. The block is a new CopperOre... err, where's the copper ore?
What you need to do now is to create a new folder in your source directory. Call it "helloworld" for good measure, and create a new file in that called "CopperOre.java". Because I wanted a few ores, but only wanted to use one block ID, my copper block ended up looking like this:
Every block has metadata, whether it likes it or not. The key is to have an itemblock for each block that exists. Create a new file named "CopperOreItem.java" in helloworld and Observe:
Next, let's add a pickaxe. This one will be simple; it will just be a normal pickaxe that uses a custom texture file.
The files are ready to use. They don't actually do anything right now, so we need to let Minecraft know that they exist. We also need to define what exactly the pickaxe is, so let's add a few bits of code relating to that as well.
This looks good and all... if you ignore the props helper. Let's not ignore it anymore; add a file named "PropsHelperHelloWorld.java" inside the helloworld folder. Try to digest as much of this information as possible; you'll need it later.
Ore needs to generate in the world, right? Right. I copied WorldGenMinable and added a value for metadata:
And then added the generators to the mod class.
The only thing left to do is add some recipes for the items, test, and call it good.
The mod_ class ended up looking like this:
-Nicolas Negroponte
-Nicolas Negroponte
Except that you seem to have forgotten to write in-game name for 'Copper Ingot', and for the ores(breaking speed).
I would love to see tutorials for ex. about items/food w/special effects/abilities, also armor.
P.S. Is there any way to make blocks drop entities on braking? Like Primed TNT, or XP
Is there any metadata-sensitive ways to set block resistance?