You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Minecraft mods, especially mods which change the client, are by and large written with Forge. If you visit their website, you'll be greeted abruptly by a mysterious message at the top of an SMF forum, with no clear path towards actually... making a mod. I'm documenting here the steps I went through to get started, in the hopes of helping the next person have an easier time of it.
I'll be using Scala for this guide, but it should be fairly easy to adapt these instructions to any JVM language (e.g. clojure or if you're feeling masochistic, Java). I'm also developing on OS X, so some of the commands will be a little different if you're on Linux or Windows. I'm assuming you have some proficiency with your operating system, so I won't go into details about how to adapt those commands to your system.
Background
Minecraft doesn't have an official mod API (despite early promises), so all mods are built off a decompiled version of the source code called Mod Coder Pack, maintained by... I have no idea who, possibly this guy who appears to work at Mojang i.e. Microsoft but still hosts files on Mediafire? MCP is essentially a set of scripts that takes the official Minecraft jar files, decompiles them, and renames all the obfuscated variables and classes. Mojang seems to be fine with this.
Forge is built on top of MCP--it takes the decompiled and deobfuscated source, then tweaks it a bunch, adding hooks in various places that your mod can attach itself to through an extensive set of source code patches. Forge is Minecraft with a mod API.
This contains a template project that uses gradle as a build system, hooking into ForgeGradle for its dirty work (decompiling and deobfuscating Minecraft, applying various patches, and so on). To get this going, you'll want to unzip the template into a new directory
$ mkdir mymod && cd mymod
$ unzip ../forge-1.8.9-11.15.1.1764-mdk.zip
This will dump a bunch of files into the current directory, something like this:
Great! That's all the stuff we need. Because I want to write my mod in Scala, I'm going to open up the build.gradle and add this line before I do anything else:
And then we want to ask gradle to pull down all the minecraft stuff it needs and do its deobfuscation magic:
$ ./gradlew setupDecompWorkspace
This downloads minecraft, decompiles it, deobfuscates the decompiled source, applies a bunch of patches, recompiles everything, and stores it all in ~/.gradle/caches/minecraft (so it's shared between projects--not editable!)
Everything's technically ready to go now. You could boot a client with ExampleMod loaded by running ./gradlew runClient. The only thing ExampleMod does is print out DIRT BLOCK >> tile.dirt during initialization; it's otherwise identical to the unmodded client. But that's our Hello World!
Jabelar, whose tips and notes have been indispensable,
BluSunrize, for making Immersive Engineering, and AlgorithmX2 for making Applied Energistics 2, both of whose open source mods have made immensely valuable objects of study.
Isaac Freeman, for finding and fixing bugs in this tutorial itself :)
If this tutorial is at all useful, it is because these others have come before me and shared their wisdom.
I really like using IntelliJ for making Minecraft mods, because it makes it super easy to browse through the deobfuscated Minecraft source code, which is often the only way to figure out how anything works. Fortunately it's super easy to set up IntelliJ with Forge.
Just hit the 'Import Project' button in IntelliJ...
... and select the build.gradle file in your mod directory. Hit 'OK' and you're in!
Importing the project doesn't automatically create any run configurations, but ForgeGradle comes with a little helper to do that for IntelliJ. Running
$ ./gradlew genIntellijRuns
will create 'Run Client' and 'Run Server' run configurations, so booting the client will be as simple as hitting Ctrl+R.
Time for some Scala
Delete the java directory (nuke it from orbit, it's the only way to be sure). Make a new scala directory underneath src/main. If it doesn't show up with a blue folder icon in IntelliJ indicating it's a directory where source code is found, you might not have added the apply plugin: 'scala' line to gradle.build. Add it and re-import the gradle project (I just blew away the .idea directory and imported from scratch when this happened to me).
Underneath src/main/scala, make some more directories for your package, something like src/main/scala/com/mymod. And therein, place the following bare-bones template, which is a Scala transliteration of the Java ExampleMod:
It's SUPER CRITICAL to add the modLanguage = "scala" argument to the @Mod annotation, or you'll get funky error messages about MyMod.<init> not being defined, like this:
net.minecraftforge.fml.common.LoaderException: java.lang.InstantiationException: com.mymod.MyMod
at net.minecraftforge.fml.common.LoadController.transition(LoadController.java:162)
at net.minecraftforge.fml.common.Loader.loadMods(Loader.java:543)
at net.minecraftforge.fml.client.FMLClientHandler.beginMinecraftLoading(FMLClientHandler.java:208)
at net.minecraft.client.Minecraft.startGame(Minecraft.java:451)
[...]
Caused by: java.lang.InstantiationException: com.mymod.MyMod
at java.lang.Class.newInstance(Class.java:427)
at net.minecraftforge.fml.common.ILanguageAdapter$JavaAdapter.getNewInstance(ILanguageAdapter.java:174)
at net.minecraftforge.fml.common.FMLModContainer.constructMod(FMLModContainer.java:534)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
[...]
See here for the code in Forge which needs this annotation.
Anyway, you should now be running the simplest possible Scala-based Minecraft mod. Congrats!
The fundamental elements of Minecraft are blocks, items and entities. Blocks are parts of the world, like dirt and obsidian. Items are things that go in your inventory like a sword or a book. Sometimes an item represents a block, like when you have a dirt block in your inventory. And lastly, entities are things that move, like chickens and players.
Blocks
Blocks form a 3D grid, with X and Z forming the horizontal plane, and Y the vertical axis. Internally, the infinite world is divided into chunks, each 16 blocks to a side and 256 blocks tall. Chunks can be individually loaded and unloaded by the server, and when you fly around the world, the server will send you chunks one by one.
In code, what Minecraft calls a Block would probably more accurately be called a BlockType, but so be it. Each block in a chunk is stored very compactly, as just 16 bits of data--12 bits for a block ID and 4 bits for metadata. In order to figure out the various other bits of information about a block--say, how to render it, or how long it should take to break the block--Minecraft looks up the Block by its ID and passes in the block position and metadata to ask it questions. Minecraft 1.8 introduced a fancy thing called BlockState to aid in interpreting those 4 bits of metadata (more on that later). If you want to store more than 4 bits of information about your block, like signs and paintings do, you'll want a TileEntity. More on that later too. Due to the fact that a block's identity is stored as a 12-bit number, there can only be up to 4096 different block types in any given Minecraft instance.
Enough talk. How do I make a block??
First, make a new class, I'm going to call mine BlockCool.
We'll put some more stuff in there later, but for now that's all we need. We extend from Block and pass Material.ground to its constructor. The Material defines a few basic properties of a block, such as whether or not it's solid, opaque, burnable, and so on. Browse the Minecraft source starting from net.minecraft.block.material.Material for examples of other materials and their use.
That's defined our block type--well, actually that's more like a block type type. In order to define a block type, we'll need an instance of BlockCool. Theoretically we could make many BlockCool instances, and each would get its own block ID and be its own block type. We could even make them render differently, despite all being of the same block type type. But we won't. Not today, anyway.
Next up we'll make an instance of BlockCool and register it with the GameRegistry to give it an ID. Add a preInit event handler to MyMod (the name of the function isn't important; Forge does reflection magic and looks at the type of the event argument):
objectMyMod {
@EventHandlerdefpreInit(event: FMLPreInitializationEvent):Unit= {
valcool=newBlockCoolGameRegistry.registerBlock(cool, "cool")
// "cool" is the name of the block, used by Minecraft as// a human-friendly version of the block ID that's created// internally. Forge will actually tweak the name to make// sure it doesn't collide with any other mods, by// prepending your mod's ID, like "mymod:cool".
}
}
NOTE: Technically, it also works to register the block in the init phase, rather than preInit. However, when we later come to registering item models, which must be done in the preInit phase, we're going to want our blocks to already be registered, so we're preparing for that. Also, Forge recommends you register blocks in preInit, so there's that.
And that's all you need! If you hit run and open the creative inventory, you'll see something like this:
That beautiful purple and black thing is our baby! Go ahead and plop one down in the world.
Amazing.
Just one thing though... I didn't actually want it to be black and purple and do nothing.
Models and textures
Let's solve the 'not black and purple' problem first, by way of a blockstates definition file. There are a bunch of different ways to affect the way a block is rendered (up to and including completely custom OpenGL code), but for this relatively normal block the easiest thing for us will be to use the model system built into vanilla Minecraft.
BlockStates and variants
Make a JSON file underneath src/main/resources/assets/mymod/blockstates, called cool.json. (The name is important, it has to match the block's name). In it, this:
The "variants" key in the blockstates JSON file can define a different model to be rendered depending on the metadata of a block. There's a fantastic explanation of the format of this file on the Minecraft wiki, which I won't repeat here, but it can do some pretty neat stuff. For now we're just going to use the default "normal" variant, which is what you get if your block doesn't use metadata at all. And just to test it's working, we're going to make our block look like a block of lapis. Since "lapis_block" doesn't have a : in it, Forge will recognise that we're referring to a vanilla asset. We'll refer to our own models later as "mymod:mymodel".
It still looks all dumb and black and purple in our hotbar though, and anyway we don't want it to look like lapis at all. To fix those issues, we're going to need to make a custom model.
Item models
Let's tackle the hotbar issue first. Make another JSON file, this time under the assets/mymod/models/item directory, and call it cool.json.
This defines an item model, which tells Minecraft how to render the item when it's in our inventory, and when it's in the world as an item (for example, in the player's hand, or lying on the floor).
To hook it up so that Minecraft knows to render the item using this item model when it's in your inventory, add this snippet to the preInit function, after the call to registerBlock():
// GameRegistry.registerBlock() also registers an item which// represents the block. findItem just looks it up by name.valcoolItem=GameRegistry.findItem(MODID, "cool")
ModelLoader.setCustomModelResourceLocation(
coolItem,
0,
newModelResourceLocation(coolItem.getRegistryName)
)
NOTE: It's important that you call ModelLoader.setCustomModelResourceLocation() during the preInit phase, otherwise it doesn't work. ModelLoader.setCustomModelResourceLocation() adds your item model registration information to a list, which is consulted during the constructor of RenderItem (of which there is one per game instance). RenderItem is constructed after preInit, but before init.
See the definition of net.minecraft.client.Minecraft.startGame() for more details (preInit is called by net.minecraftforge.fml.client.FMLClientHandler.instance().beginMinecraftLoading(), and init is called by net.minecraftforge.fml.client.FMLClientHandler.instance().finishMinecraftLoading()).
Begone, vile magenta! I banish ye forever.
Aside: vanilla assets
It's super helpful to peek at the vanilla Minecraft assets to see how to structure your own. You can unzip the Minecraft JAR file (which ForgeGradle kindly downloaded for you) to browse them easily. For example:
wherein you will find all the vanilla blockstate definitions, textures and models.
I copied the item model definition from the vanilla lapis block item file, which you can see at vanilla-assets/assets/minecraft/models/item/lapis_block.json if you unzip the vanilla assets (see below).
Block models
To banish the lapis, though, we'll need to make a new model. We could do all sorts of fancy stuff here, but we're just going to use the basic 'cube' model and change its texture. Baby steps.
Drop a texture (I recommend this one: ) in assets/mymod/textures/block/cool.png, and make a new JSON file in assets/mymod/models/block/cool.json:
This defines a block model based on the cube_all builtin model, and overrides the "all" texture with our own. (You can check out the cube_all model at vanilla-assets/assets/minecraft/models/block/cube_all.json, and see that it's in turn based on the cube model, which defines a single cuboid element. More on the model format here)
Update your item model to have "parent": "mymod:block/cool" instead of "parent": "block/lapis_block", and also update the blockstates file to specify the model as "mymod:cool" (not "mymod:block/cool"; Forge in its infinite wisdom adds the block/ part for you).
Et voilà:
Interlude: Client and Server
While developing, you've been using what's called the 'combined client'. Single-player Minecraft is actually just multi-player Minecraft with the server and client running in the same process. If you try to run the dedicated server with the current code (either through the 'Minecraft Server' run configuration in IntelliJ or via ./gradlew runServer), you'll get this nasty exception:
java.lang.NoClassDefFoundError: net/minecraft/client/resources/model/ModelResourceLocation
[...]
Caused by: java.lang.ClassNotFoundException: net.minecraft.client.resources.model.ModelResourceLocation
[...]
Caused by: net.minecraftforge.fml.common.asm.ASMTransformerWrapper$TransformerException: Exception in class transformer net.minecraftforge.fml.common.asm.transformers.SideTransformer@1200458e from coremod FMLCorePlugin
[...]
Caused by: java.lang.RuntimeException: Attempted to load class net/minecraft/client/resources/model/ModelResourceLocation for invalid side SERVER
This is because the dedicated server doesn't include any of the client-side rendering logic. The server JAR doesn't contain the ModelResourceLocation class, which we're using in our preInit function, so when the server tries to initialize our mod, it can't find that class and crashes.
To address this issue, Forge has a utility class called SidedProxy, which allows a class to behave differently depending on whether it's loaded on the client or the server. Refactoring to use this, we get:
@Mod(modid =MyMod.MODID, version =MyMod.VERSION, modLanguage ="scala")
objectMyMod {
finalvalMODID="mymod"finalvalVERSION="1.0"defregisterBlocks() = {
valcool=newBlockCoolGameRegistry.registerBlock(cool, "cool")
}
defregisterItemModels():Unit= {
valcoolItem=GameRegistry.findItem(MODID, "cool")
ModelLoader.setCustomModelResourceLocation(
coolItem,
0,
newModelResourceLocation(coolItem.getRegistryName)
)
}
@SidedProxy(
clientSide ="com.mymod.ClientOnlyProxy",
serverSide ="com.mymod.CommonProxy"
)
// Forge will fill this in during mod loading.varproxy:CommonProxy=null@EventHandlerdefpreInit(event: FMLPreInitializationEvent):Unit= {
proxy.preInit()
}
}
classCommonProxy {
defpreInit():Unit= {
MyMod.registerBlocks()
}
}
classClientOnlyProxyextendsCommonProxy {
overridedefpreInit():Unit= {
super.preInit()
MyMod.registerItemModels()
}
}
In the client, proxy will contain an instance of ClientOnlyProxy, but in the dedicated server, it will contain an instance of the base class CommonProxy. It's also possible to have another subclass for dedicated-server-only code, as long as it also extends CommonProxy.
You might also see annotations on functions like @SideOnly(Side.CLIENT) when browsing other Forge mod code. Forge does horrible things with ClassReader to make those functions only appear on either the annotated side. Use at your peril.
Items
Like Block, instances of the Item class represent types of items. A specific item (or stack of items) is represented by an instance of the ItemStack class. An ItemStack can have metadata, often used to store durability (up to 15 bits, it's stored as a short but restricted to be non-negative) and sometimes called 'damage' in the code. An ItemStack can also have an NBT tag associated with it, containing arbitrary extra data--think enchantments, Tinkers Construct item compositions, and so on.
Let's make an item. I want to make a "wheat and steel" item, like flint and steel but instead of lighting things on fire it plants wheat. I'm going to start by looking at the vanilla source code for flint and steel, which you can see in IntelliJ by double-tapping Shift and typing in ItemFlintAndSteel. This is roughly the strategy I'd suggest for doing anything with Minecraft (or really, anything in programming): find something that's a little like what you want, learn from it and adapt it to your own nefarious wheat-making purposes. The vanilla source code is your friend, as is GitHub search.
Start by making a new Item subclass, called ItemWheatAndSteel:
It doesn't do anything yet, though. Let's fix that. Override the onItemUse function in ItemWheatAndSteel like this:
overridedefonItemUse(
stack: ItemStack, // The ItemStack the player is usingplayer: EntityPlayer, // The player using the itemworld: World, // The world in which the block existspos: BlockPos, // The position of the block the item is being used onside: EnumFacing, // The side of the block being pointed athitX: Float, // Exact coordinates the mouse is pointing athitY: Float,
hitZ: Float
):Boolean= {
// If the player is pointing at the top face of a block, and there's// nothing above it...if (side ==EnumFacing.UP&& world.isAirBlock(pos.up)) {
// ... get the Minecraft item called "wheat_seeds" ...valwheat_seeds=GameRegistry.findItem("minecraft", "wheat_seeds")
// ... which we know implements IPlantable ...
.asInstanceOf[ItemwithIPlantable]
// ... check if the block the player is pointing at can grow wheat (i.e.// it's farmland)valblock= world.getBlockState(pos).getBlock
valcanGrow= block.canSustainPlant(world, pos, EnumFacing.UP, wheat_seeds)
if (canGrow) {
// ... if it is, set the block above the pointed-at block to wheat ...
world.setBlockState(pos.up, wheat_seeds.getPlant(world, pos))
// ... and add one point of damage to the wheat & steel.
stack.damageItem(1, player)
// The return value of onItemUse indicates whether the use was successful,// which is used to determine whether to play the "swing item" character// animation.returntrue
}
}
false
}
NOTE: You'll have to be in survival mode for the items to get damaged. See the source for ItemStack.damageItem(), which begins with a check for creative mode.
You still have to hoe the ground before planting the wheat, though. It'd be neat if it would automatically hoe the dirt, too... (hint: look at how ItemHoe works.)
Bedrock miner link is down, I suggest linking to the archive.org version instead. rip