Also, make sure you're using the latest recommended release for Forge, currently 10.12.0.1024. If you try using earlier version of Forge 1.7, many of the method names will likely not be the same. If you need help setting up your programming environment, that describes the process.
1. Add new Block and Creative Tab
First, here's a very basic mod class to begin with:
package mymod; import net.minecraft.block.Block; import net.minecraft.creativetab.CreativeTabs; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid = MyMod.MODID, version = MyMod.VERSION) public class MyMod { public static final String MODID = "mymod"; public static final String VERSION = "0.1"; public static Block blockTest; public static CreativeTabs tabMyMod = new CreativeTabsMyMod("MyMod"); @EventHandler public void preInit(FMLPreInitializationEvent event) { blockTest = new BlockTest().setBlockName("blockTest"); GameRegistry.registerBlock(blockTest, blockTest.getUnlocalizedName().substring(5)); } }
You can adjust these two lines to provide a unique Mod ID and a version for your mod. I've been told the Mod ID must be all lowercase letters now.
public static final String MODID = "mymod"; public static final String VERSION = "0.1";
This line declares the block:
public static Block blockTest;
This line sets up a new creative tab:
public static CreativeTabs tabMyMod = new CreativeTabsMyMod("MyMod");
And these two lines are used to register the block using the unlocalized name "blockTest":
blockTest = new BlockTest().setBlockName("blockTest"); GameRegistry.registerBlock(blockTest, blockTest.getUnlocalizedName().substring(5));
If you're upgrading from previous versions of MC, note that blocks must now be registered inside a FMLPreInitializationEvent, FMLInitializationEvent no longer works.
Now we need two more classes, one for the block, and one for the creative tab. I'll start with the block:
package mymod; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.creativetab.CreativeTabs; public class BlockTest extends Block { protected BlockTest() { super(Material.ground); this.setCreativeTab(MyMod.tabMyMod); } }
This is about as basic as you can get for a block, super(Material.ground); sets the material type for the block and this.setCreativeTab(MyMod.tabMyMod); sets which creative tab that you see the block in.
Finally, here's the code to set up a creative tab:
package mymod; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.init.Blocks; import net.minecraft.item.Item; public class CreativeTabsMyMod extends CreativeTabs { public CreativeTabsMyMod(String tabLabel) { super(tabLabel); } @Override @SideOnly(Side.CLIENT) public Item getTabIconItem() { return Item.getItemFromBlock(Blocks.dirt); } }
The part you probably want to change is Item.getItemFromBlock(Blocks.dirt). The getTabIconItem() method returns an Item and uses its icon for the creative tab. I wanted to use the dirt block icon, so I used Item.getItemFromBlock() to change the dirt block (Blocks.dirt) into an Item type.
That's it for now! You may have noticed this mod is missing some rather important things, such as an icon for the block and localized names. I should be adding those to the tutorial later as I discover how to set all that up myself.
2. Language Localization
If you try running the mod, you'll notice the block and the creative tab have odd names in game. The block will be called something like tile.blockTest.name, where "blockTest" is the unlocalized name for the block. Because minecraft is translated into many many different languages, the name for the block that's used in the code doesn't use any specific language. You have to add a file to tell MC how to translate unlocalized names into whichever language the user has chosen, a process known as language localization.
Other tutorials say you're supposed to use different folders for the language files, but there's only one location I've gotten able to work with forge v1024. Within your forge directory navigate to "src\main\resources\" and create a folder named assets. Within this folder create another folder with your Mod ID as the name, and then within that folder, create a folder named "lang". Using the code from the first tutorial, it should look something like this:
forge-directory\src\main\resources\assets\mymod\lang
The lang folder is where you'll be putting all your localization files. For instance, I speak US English, so I'll want to create a localization file named "en_US.lang". For other language codes, you can check "forge-directory\eclipse\assets\lang" for a long list of examples. Within the localization file you should have something that looks like this:
tile.blockTest.name=Test Block itemGroup.MyMod=My Mod
tile.blockTest.name uses the unlocalized name of the block we added in the first tutorial, and "Test Block" is the name we want to display in game. itemGroup.MyMod is the unlocalized name of the creative tab, and if you're not sure about a specific unlocalized name, you can always hover your mouse over an object in game and if it hasn't been named yet, it will give you the unlocalized name.
If you've done everything correctly, you should be able to start the game now and see the new names you've set.
3. Add a texture to your block
If you've followed the tutorial so far, your block should have a magenta and black checkered pattern in game. That's the default texture that is set when MC can't find any other texture to use for the block. Setting your own texture is relatively simple, we just have to declare a new variable and override a couple methods from the Block class.
Update: If you only have a single block texture you want to use, this tutorial is a bit of overkill. To make things much simpler you can use the method setBlockTextureName(MyMod.MODID + ":" + "blockTest") when registering the block, or within your block's class constructor. If you do that, you can skip the rest of this tutorial, which sets you up for using multiple textures with a block. Thanks to Sequiturian for the tip!
First, in the BlockTest class you want to add:
@SideOnly(Side.CLIENT) protected IIcon blockIcon;
This creates a variable called blockIcon to store the texture in. Also, notice the @SideOnly(Side.CLIENT). Minecraft separates the client and server, and since the graphics are handled on the client side, you add this line so they're only loaded on the client.
Next we add this method:
@SideOnly(Side.CLIENT) @Override public void registerBlockIcons(IIconRegister p_149651_1_) { blockIcon = p_149651_1_.registerIcon(MyMod.MODID + ":" + this.getUnlocalizedName().substring(5)); }
This registers the texture, and lets the client know where to find the texture. If you're using the code from the tutorial so far, it will use the following location for textures:
forge-directory\src\main\resources\assets\mymod\textures\blocks
And it will search for a file named "blockTest.png" in that directory to use as the texture. Note that "mymod" is what I set the mod ID to in the first tutorial, and "blockTest" is the unlocalized name for the block. Finally, we need this last method:
@SideOnly(Side.CLIENT) @Override public IIcon getIcon(int p_149691_1_, int p_149691_2_) { return blockIcon; }
This method returns the actual texture we want to use for the block, which was set in the registerBlockIcons() method. This should be all you need to get block textures working, though this only works properly for very simple blocks. Blocks with different textures per side, and blocks with different textures based upon metadata will not work properly. I believe the two parameters on the getIcon() method will give you the side and the metadata, though I'll save how to use those for another tutorial.
The class for the block we added should now look something like this:
package mymod; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.util.IIcon; public class BlockTest extends Block { @SideOnly(Side.CLIENT) protected IIcon blockIcon; protected BlockTest() { super(Material.ground); this.setCreativeTab(MyMod.tabMyMod); } @SideOnly(Side.CLIENT) @Override public void registerBlockIcons(IIconRegister p_149651_1_) { blockIcon = p_149651_1_.registerIcon(MyMod.MODID + ":" + this.getUnlocalizedName().substring(5)); } @SideOnly(Side.CLIENT) @Override public IIcon getIcon(int p_149691_1_, int p_149691_2_) { return blockIcon; } }
4. Sided block textures
First, I'm gonna share a quick tip on how to find out on your own how a method works. I want to know what the two parameters on getIcon(int p_149691_1_, int p_149691_2_) do, so I'll use
System.out.println(p_149691_1_ + " " + p_149691_2_);
in the getIcon() method to output the values for each parameter to the console in Eclipse. Turns out p_149691_1_ counts from 0 to 5, while p_149691_2_ stays zero. I know the block texture can be set differently for the 6 different sides of the block, and I haven't set the metadata, so that should be zero. It should now be obvious which one I need to use to set sided textures.
First, I'm going to add a new IIcon object:
protected IIcon blockIconTop;
Then add this line in registerBlockIcons():
blockIconTop = p_149651_1_.registerIcon(MyMod.MODID + ":" + this.getUnlocalizedName().substring(5) + "Top");
Note that I'm adding '+ "Top"' to the end of the string, this means it will look for blockTestTop.png in the block texture directory.
Lastly, I need to change the getIcon() method so it returns blockIconTop for the top side of the block. I looked in the BlockGrass class to find that "1" corresponds to that side of a block. Here's my new method, and I've changed the parameters to make it easier to understand:
@SideOnly(Side.CLIENT) @Override public IIcon getIcon(int side, int metadata) { if (side == 1) { return blockIconTop; } else { return blockIcon; } }
If you add a texture image named blockTestTop.png to your block texture directory ( forge-directory\src\main\resources\assets\mymod\textures\blocks ), you should now see that texture on the top side of the block when running MC.
Here's what my full BlockTest class looks like now:
package mymod; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.util.IIcon; public class BlockTest extends Block { @SideOnly(Side.CLIENT) protected IIcon blockIcon; protected IIcon blockIconTop; protected BlockTest() { super(Material.ground); this.setCreativeTab(MyMod.tabMyMod); } @SideOnly(Side.CLIENT) @Override public void registerBlockIcons(IIconRegister p_149651_1_) { blockIcon = p_149651_1_.registerIcon(MyMod.MODID + ":" + this.getUnlocalizedName().substring(5)); blockIconTop = p_149651_1_.registerIcon(MyMod.MODID + ":" + this.getUnlocalizedName().substring(5) + "Top"); } @SideOnly(Side.CLIENT) @Override public IIcon getIcon(int side, int metadata) { if (side == 1) { return blockIconTop; } else { return blockIcon; } } }
5. Add an item
If you understood the process of adding a block in the first tutorial, adding a new item should be cake. I'm going to use the same basic mod code I used in the first tutorial, and add this line to declare an object of the Item type named itemTest. It should go right next to where blockTest was declared.
public static Item itemTest;
Next I'll add this line inside the preInit() method. You can also just use Item() instead of ItemTest() if you don't want to use a custom class for your new item.
itemTest = new ItemTest().setUnlocalizedName("itemTest").setTextureName(MyMod.MODID + ":" + "itemTest");
I've gone over how the unlocalized name works in previous tutorials, so I'll focus upon the setTextureName() method. It takes a string in the following format "ModID:texture-name", which in this case is "mymod:itemTest". This tells minecraft where to look for your textures, "mymod" is the assets directory, and since it's looking for an item texture, its going to search within the "textures/items" directory within "mymod". Within that directory, it's going to look for an image named "itemTest.png", so the full path should be along the lines of forge-directory\src\main\resources\assets\mymod\textures\items\itemTest.png .
One more line to add to our mod file within the preInit() method:
GameRegistry.registerItem(itemTest, itemTest.getUnlocalizedName().substring(5));
This is what registers the item, i.e. the part that adds your item into the game itself. Finally, we need to create a new class file named ItemTest, I kept mine super-simple, all it does is set which creative tab to use.
package mymod; import net.minecraft.item.Item; public class ItemTest extends Item{ protected ItemTest() { this.setCreativeTab(MyMod.tabMyMod); } }
And that's it! You should now have a new item in-game. Here's my full mod class now if you need to refer to it:
package mymod; import net.minecraft.block.Block; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.Item; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid = MyMod.MODID, version = MyMod.VERSION) public class MyMod { public static final String MODID = "mymod"; public static final String VERSION = "0.1"; public static Block blockTest; public static Item itemTest; public static CreativeTabs tabMyMod = new CreativeTabsMyMod("MyMod"); @EventHandler public void preInit(FMLPreInitializationEvent event) { blockTest = new BlockTest().setBlockName("blockTest"); itemTest = new ItemTest().setUnlocalizedName("itemTest").setTextureName(MyMod.MODID + ":" + "itemTest"); GameRegistry.registerBlock(blockTest, blockTest.getUnlocalizedName().substring(5)); GameRegistry.registerItem(itemTest, itemTest.getUnlocalizedName().substring(5)); } }
Whoops, I seem to have forgotten something important that I covered in a previous tutorial, notice what it is? When I load up the game, my item is named item.itemTest.name, the name hasn't been localized! No worries, I just need to add this line to my language localization file: item.itemTest.name=Item Test
6. Add an entity
It takes a few lines of code to add an entity, and if you've got a lot of entities it can be a pain to write out the code over and over again. To streamline the process, I use a method to take care of all the repetitive work which I've inserted into my mod class:
public static void registerEntity(Class entityClass, String name) { int entityID = EntityRegistry.findGlobalUniqueEntityId(); long seed = name.hashCode(); Random rand = new Random(seed); int primaryColor = rand.nextInt() * 16777215; int secondaryColor = rand.nextInt() * 16777215; EntityRegistry.registerGlobalEntityID(entityClass, name, entityID); EntityRegistry.registerModEntity(entityClass, name, entityID, instance, 64, 1, true); EntityList.entityEggs.put(Integer.valueOf(entityID), new EntityList.EntityEggInfo(entityID, primaryColor, secondaryColor)); }
The first line within registerEntity() is to get a unique ID for the entity. The next four lines generate random colors for the spawn egg using the entity's name as a random seed. If you want to set colors yourself, you could easily adjust the method to take the primary and secondary colors as method parameters.
EntityRegistry.registerGlobalEntityID(entityClass, name, entityID); EntityRegistry.registerModEntity(entityClass, name, entityID, instance, 64, 1, true); EntityList.entityEggs.put(Integer.valueOf(entityID), new EntityList.EntityEggInfo(entityID, primaryColor, secondaryColor));
These lines register the entity ID, register the entity itself, and add a spawn egg respectively. It's mostly the same as MC 1.6, though I had to change EntityEggInfo to EntityList.EntityEggInfo. You may have noticed we haven't declared the instance variable yet, so let's add this line to our mod class. Put it near the top, but underneath the MODID string declaration.
@Instance(MODID) public static MyMod instance;
One more thing the mod class needs, and that's to actually call the method we just created. Put this line within the preInit() method:
registerEntity(EntityTest.class, "entityTest");
Okay, now we need to make the EntityTest class that I just referred to for our entity. Here's some very basic code that's simplified to the point of not really being practical:
package mymod; import net.minecraft.entity.monster.EntityMob; import net.minecraft.world.World; public class EntityTest extends EntityMob{ public EntityTest(World par1World) { super(par1World); } }
This class extends EntityMob, which are the hostile monster types of entities. EntityCreature, EntityAnimal, and EntityLiving are all examples of other entity types that you can extend.
Finally, we give a localized name to our new entity by putting this into our language localization file:
entity.entityTest.name=Entity Test
That should be it! If you start minecraft, you should see a new spawn egg under the Miscellaneous creative tab. Using it should create a retarded white box within the game. Not very useful, but not bad to begin with! I'll add more tutorials about making entities smarter and more attractive.
7. Give entity a model
If you're not very comfortable coding Java, you may want to stick to easier things like blocks and items. These tutorials are going to start getting a bit more complicated now.
If you want to mod Minecraft properly, you have to understand how the client-server model works. All the rendering, the parts of the code that display graphics, happen on the client side. This isn't something the server needs to deal with at all. To separate the client and server code we're now going to create proxies, and then use the client proxy to give a proper model to our entity.
First, you'll need this in your main mod class:
@SidedProxy(clientSide="mymod.proxy.ClientProxy", serverSide="mymod.proxy.CommonProxy") public static CommonProxy proxy;
This tells Forge which classes we want to use for the client and server proxies. Notice it refers to a new java package, "mymod.proxy" so go ahead and create that now. Next create the server proxy class, CommonProxy, and paste this code inside:
package mymod.proxy; public class CommonProxy { public void registerRenderers() { // Nothing here as the server doesn't render graphics or entities! } }
Not much there since we don't need to do anything with the server side at the moment. Next create a new class named ClientProxy and paste this code inside:
package mymod.proxy; import net.minecraft.client.model.ModelBiped; import mymod.EntityTest; import mymod.RenderTest; import cpw.mods.fml.client.registry.RenderingRegistry; public class ClientProxy extends CommonProxy{ @Override public void registerRenderers() { RenderingRegistry.registerEntityRenderingHandler(EntityTest.class, new RenderTest(new ModelBiped(), 0.5F)); } }
The RenderingRegistry.registerEntityRenderingHandler() method tells MC how to render your new entity. The RenderTest class will be used to render EntityTest entities and it will use the ModelBiped class for the model. ModelBiped is the basic player model. Also, the float that's the second parameter in RenderTest determines the shadow size.
Now we'll need to add a call to registerRenderers() within our main mod class. I put this line at the end of the preInit() method:
proxy.registerRenderers();
Next, create the RenderTest class, and paste this code inside:
package mymod; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.renderer.entity.RenderBiped; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; public class RenderTest extends RenderBiped { private static final ResourceLocation textureLocation = new ResourceLocation(MyMod.MODID + ":" + "textures/models/entityTest.png"); public RenderTest(ModelBiped model, float shadowSize) { super(model, shadowSize); } @Override protected ResourceLocation getEntityTexture(Entity par1Entity) { return textureLocation; } }
The class constructor is simple enough, we just pass the parameters on to the RenderBiped class, the superclass of RenderTest. The rest of this code is used to set the texture, getEntityTexture() returns textureLocation, which *gasp* holds the location of the texture. We set it similar to the same way we set block and item textures, but we use a ResourceLocation type and I added some more information on what directory to find the texture in.
Since I'm using the same model the player uses, any player skin should work fine as a texture for my entity. I'm just testing things out, so I'm going to use the Steve skin, which will go in the location we set using ResourceLocation(MyMod.MODID + ":" + "textures/models/entityTest.png") . If you haven't tweaked your MODID, the path to the texture should be something like this: forge-directory\src\main\resources\assets\mymod\textures\models\entityTest.png
And that should be it, if you start minecraft and spawn the entity, it should now be using the basic biped model with the Steve skin.
8. Entity attributes and AI
If you used the previous tutorials to create an entity, you may have noticed that when you spawn it, it moves painfully slow. We need to set entity attributes, so go ahead and add this code to your entity's class:
@Override protected void applyEntityAttributes() { super.applyEntityAttributes(); this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(20.0D); this.getEntityAttribute(SharedMonsterAttributes.followRange).setBaseValue(32.0D); this.getEntityAttribute(SharedMonsterAttributes.knockbackResistance).setBaseValue(0.0D); this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.25D); this.getEntityAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(2.0D); }
This code should be fairly self-explanatory, you get the attribute, for instance maxHealth, and then use setBaseValue() to set the value. I should explain knockbackRestistance some, which ranges from 0.0D to 1.0D (the D stands for double, a primitive data type). 0.0D means 0% knockback resistance, so the entity will be knocked back every hit. 1.0D means 100% knockback resistance, so the entity will never be knocked back.
An important thing to understand abbout attributes is that they're saved per entity. This means you can do things like set them to random values or even alter an entity's attributes after it's been spawned, though I wont go in to how to do that here.
After adding attributes, it may be that you're happy with the way your entity behaves, and in that case there's no reason to add any "advanced" artifical intelligence (AI). However, if you do want more control over your entity's behavior you'll need to add this method:
public boolean isAIEnabled() { return true; }
Now this method by itself will just make your entity stand there and stare straight forward. When you've got AI enabled, you have to tell your entity specifically how to behave. We'll do that by adding AI tasks to your entity's class constructor. Here's what I used:
this.tasks.addTask(1, new EntityAISwimming(this)); this.tasks.addTask(2, new EntityAIAttackOnCollide(this, EntityPlayer.class, 1.2D, false)); this.tasks.addTask(3, new EntityAIWander(this, 1.0D)); this.tasks.addTask(4, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F)); this.tasks.addTask(5, new EntityAILookIdle(this)); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityPlayer.class, 0, true));
The numbers that are the first parameter of the addTask method determine the priority of the task. We put EntityAISwimming as 1, since we don't want our entity to drown in water. The EntityAIAttackOnCollide is the basic attack type that mobs like zombies and spiders use. EntityAIWander tells the mob to wander around when it's not attacking. I'll let you figure out what EntityAIWatchClosest does, and EntityAILookIdle makes the mob look around when it's just standing there.
Then there's the target tasks, which lets the entity know how to choose attack targets. EntityAIHurtByTarget will target any mob that does damage to our entity, and EntityAINearestAttackableTarget will target any nearby entity that matches the second parameter, in this case EntityPlayer.class.
That should be it, if you run the game your entity should now behave like a regular mob. One thing to be careful about is any mobs that were created in your game before you changed attributes around. You'll have to spawn new entities for them to use the new attributes you set.
Other 1.7.2 tutorials
Here's some other tutorials to check out:
- [1.7.2][1.6.4] EventHandler and IExtendedEntityProperties - coolAlias
- [1.7.2][1.6.4] Custom Inventories in Items and Players - coolAlias
- Netty Packet Handling - Sirgingalot - (learn to send packets between the client & server)
- Basic Blocks - Havvy - (more information on Blocks than this tutorial)
- Wuppy's 1.7 Tutorials
Happy modding!
44