Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

My guide to 1.12.2 modding


Recommended Posts

There are a ton of guides, and I felt like I just needed to make one of how I mod using 1.12.2.


Table of Contents:

1. Getting Started

2. Registries

3. Proxies

3. Basic Block/Item

4. GUIs

(There will be more coming, I am writing this as I learn.)


Getting Started:


Download Forge mdk

Unzip it

Run gradlew setupDecompWorkspace

for Eclipse:

Run gradlew eclipse in cmd.exe

for Intellij:

Run gradlew setupDevWorkspace ideaModule

Import build.gradle as a project

Go to File -> Project Structure -> Modules

Click '+' and click import, choose (folder name).iml. (There is only one in the folder)

Close intellij

run gradlew genIntellijruns

Open Intellij

Run -> Profile -> Edit Configurations

Choose server/client

Set "Use classpath of module" to (folder name)_main





Registries are Forge's way of injecting instances of your classes into the game.


Basic setup:


You need a class annotated with EventBusSubscriber

From there you add methods annotated with EventHandler to register blocks.


public static void registerBlocks(RegistryEvent.Register<Block> event) {

blocks is a List of all blocks in the mod.


Note: When registering blocks, you also have to register an ItemBlock.

To do this use the Item registry event, and register a new instance of ItemBlock(you have to set its registry name).


Registering models:

(This may need to be client side only, if it is please tell me)

public static void registerModels(ModelRegistryEvent event) {
    for (Block block : blocks) {
        if (block.getRegistryName() != null)
            ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(block), 0, new ModelResourceLocation(block.getRegistryName(),"inventory"));

Pretty simple, nothing too hard.




What is ObjectHolder?

ObjectHolder is used to get instances of your block.


public class ModBlocks {
    public static final Block MOD_BLOCK = null;

After the block is registered, an instance of it will be injected into MOD_BLOCK.




Minecraft runs on 2 sides. Client and server.

Certain things are only client side, some are only server side.


Setting up proxies:


Proxies require 2 classes, Client and Server.

The SidedProxy annotation requires 2 arguments, the path to your client and server proxy (ex. "com.author.mod.proxies.ClientProxy")

Then you can use the SidedProxy annotation with the argument saying what side it is to tell Forge to run that code on a certain side.

If you want to check what side a method is, use world.isRemote(), true means client side, false means server side.




This is pretty simple, just make a class extend Block/Item, put in the necessary methods and constructors (setRegistryName() is a big one).

Then register an instance of that block/item in the appropriate registry.


When making a texture/model, you need to have the appropriate files. (BlockState for a block, block model for blocks, item model for blocks and items, and textures)

I suggest looking at the default MC asset files when making textures. I won't get too much into detail here because it's not that hard to do.




GUIs are a bit harder.

A GUI can either be a container, or a screen.


A GUI requires 2-3 classes bare minimum, depending on what type it is.


Simple screen:


Create a class that implements IGuiHandler.

That class needs 2 overrided methods.




public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
    return new Container(player.inventory, world);

public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
    return new Gui(new Container(player.inventory, world));


Now you gotta register it in your main class's init method:

NetworkRegistry.INSTANCE.registerGuiHandler(instance, new WorkbenchGuiHandler());

(instance is the instance of your main class)

Note: the instance should be obtained by making a variable and annotating it with the Instance annotation, and your modid as an argument.


Now, we have to make the actual GUI. For my example I am going to be making a custom crafting table.


Here is the GUI screen with the crafting table background:


 GuiScreen(Container inventorySlotsIn) {

//This is for everything behind the items
protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
    GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
    //Makes the background darker like in a normal craftring table
    //Size of the GUI image
    int i = (this.width - this.xSize) / 2;
    int j = (this.height - this.ySize) / 2;
    //Load the default crafting GUI texture
    this.mc.getTextureManager().bindTexture(new ResourceLocation("textures/gui/container/crafting_table.png"));
    //Draw it
    this.drawTexturedModalRect(i, j, 0, 0, this.xSize, this.ySize);

//Now for on top of the items
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
    //Draw the name at the top, getting the name from the lang file.
    this.fontRenderer.drawString(I18n.format("gui.custom_workbench.name"), 28, 6, 4210752);


Now we need a container to contain all of the items. the container has to draw the player's inventory, the inventory, listen for click events, etc...


just saying, this one's a big class.


Commented code is code for making the inventory a crafting table


public class GUIContainer extends Container {
    private World world;
    private InventoryCrafting matrix;
    private InventoryCraftResult result;
    private EntityPlayer player;

    GUIContainer(InventoryPlayer playerInventory, World world) {
        //matrix = new InventoryCrafting(this, 3, 3);
        player = playerInventory.player;
        this.world = world;
        //result = new InventoryCraftResult();

        int index = 1;
        Output slot, code down below.
        addSlotToContainer(new CraftResultSlot(matrix, 0, 124, 35));
        Crafting matrix
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                addSlotToContainer(new Slot(matrix, index, 30 + j * 18, 17 + i * 18));
                index += 1;
        //Player's inventory
        for (int k = 0; k < 3; k++) {
            for (int i = 0; i < 9; i++) {
                addSlotToContainer(new Slot(playerInventory, index, 8 + i * 18, 84 + k * 18));
                index += 1;
		//Player's hotbar
        for (int i = 0; i < 9; i++) {
            addSlotToContainer(new Slot(playerInventory, i, 8 + i * 18, 142));
    //Checking if player can open the inventory
    public boolean canInteractWith(EntityPlayer playerIn) {
        Code to check if it is a custom crafting table
        Getting the block the player is looking at, with a max distance of 6
        RayTraceResult ray = playerIn.rayTrace(6, 1.0f);
        if (ray == null) return false;
        //Get the position of the block
        BlockPos pos = ray.getBlockPos();
        //Get the world
        World world = playerIn.getEntityWorld();
        //And the block
        Block block = world.getBlockState(pos).getBlock();
        //Check if it is our block
        return block.equals(ModBlocks.CUSTOM_WORKBENCH);
        return true;

    public void onContainerClosed(EntityPlayer playerIn) {

        drop the items in the crafting grid
        if (!world.isRemote) {
            for (int i = 0; i < 9; ++i) {
                ItemStack itemstack = matrix.removeStackFromSlot(i);

                if (!itemstack.isEmpty()) {
                    playerIn.dropItem(itemstack, false);

    //Code when item is shift clicked
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index) {
        ItemStack itemstack = ItemStack.EMPTY;
        Slot slot = inventorySlots.get(index);

        if (slot != null && slot.getHasStack()) {
            ItemStack currentItem = slot.getStack();
            itemstack = currentItem.copy();

            if (index == 0) {
                currentItem.getItem().onCreated(currentItem, world, playerIn);
                if (!mergeItemStack(currentItem, 37, 46, false)) {
                    return ItemStack.EMPTY;
                slot.onSlotChange(currentItem, itemstack);
            if (index >= 11 && index < 38) {
                //if (!mergeItemStack(currentItem, 37, 46, false)) {
                    //if (recipe != null)
                        //Put correct output in output slot
                        //putStackInSlot(0, recipe.output);
                    //return ItemStack.EMPTY;
                //This might not work!
                if(!mergeItemStack(currentItem, 0, 8, false)) {
                    return ItemStack.EMPTY;
            } //else if (index >= 38 && index < 46) {
                //if (!mergeItemStack(currentItem, 10, 37, false)) {
                    //return ItemStack.EMPTY;
            //} else if (!mergeItemStack(currentItem, 10, 46, false)) {
                //return ItemStack.EMPTY;

            if (currentItem.isEmpty()) {
            } else {

            if (currentItem.getCount() == itemstack.getCount()) {
                return ItemStack.EMPTY;
            slot.onTake(playerIn, currentItem);

        return itemstack;

    public boolean canMergeSlot(ItemStack stack, Slot slotIn) {
        return /*slotIn.inventory != result && */super.canMergeSlot(stack, slotIn);

    public void onCraftMatrixChanged(IInventory in) {
        IRecipe recipe = CraftingManager.findMatchingRecipe(matrix, world);
        if (recipe != null)
              putStackInSlot(0, recipe.getRecipeOutput());


If you want the CraftResultSlot code, here it is:

public class CraftResultSlot extends Slot {
    public CraftResultSlot(InventoryCrafting matrix, int index, int x, int y) {
        super(matrix, index, x, y);

    //Disable items getting put in the slot
    public boolean isItemValid(ItemStack stack) {
        return false;


Please comment anything i missed, things I should add, errors, etc...


I will expand this soon!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing

    No registered users viewing this page.

  • Posts

    • Thanks for the help everybody! I tried this and although the block did register, the fluid just appears as a flat plane you can only see if you break the block under it. I tried debugging it as much as I could but alas I couldn't solve it Here is the code for my "fixed" liquidblock as suggested by Luis_ST: package com.hotmail.majdroaydi.minitech.blocks; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.sounds.SoundEvent; import net.minecraft.tags.FluidTags; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FlowingFluid; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import java.util.Optional; import java.util.function.Supplier; public class ForgeLiquidBlock extends LiquidBlock { public ForgeLiquidBlock(Supplier<? extends FlowingFluid> supplier, Properties properties) { super(supplier, properties); } @Override public VoxelShape getCollisionShape(BlockState p_54760_, BlockGetter p_54761_, BlockPos p_54762_, CollisionContext p_54763_) { return p_54763_.isAbove(STABLE_SHAPE, p_54762_, true) && p_54760_.getValue(LEVEL) == 0 && p_54763_.canStandOnFluid(p_54761_.getFluidState(p_54762_.above()), getFluid()) ? STABLE_SHAPE : Shapes.empty(); } @Override public boolean isPathfindable(BlockState p_54704_, BlockGetter p_54705_, BlockPos p_54706_, PathComputationType p_54707_) { return !getFluid().is(FluidTags.LAVA); } @Override public boolean skipRendering(BlockState p_54716_, BlockState p_54717_, Direction p_54718_) { return p_54717_.getFluidState().getType().isSame(getFluid()); } @Override public void onPlace(BlockState p_54754_, Level p_54755_, BlockPos p_54756_, BlockState p_54757_, boolean p_54758_) { if (this.shouldSpreadLiquid(p_54755_, p_54756_, p_54754_)) { p_54755_.getLiquidTicks().scheduleTick(p_54756_, p_54754_.getFluidState().getType(), getFluid().getTickDelay(p_54755_)); } } @Override public BlockState updateShape(BlockState p_54723_, Direction p_54724_, BlockState p_54725_, LevelAccessor p_54726_, BlockPos p_54727_, BlockPos p_54728_) { if (p_54723_.getFluidState().isSource() || p_54725_.getFluidState().isSource()) { p_54726_.getLiquidTicks().scheduleTick(p_54727_, p_54723_.getFluidState().getType(), getFluid().getTickDelay(p_54726_)); } //return super.updateShape(p_54723_, p_54724_, p_54725_, p_54726_, p_54727_, p_54728_); return p_54723_; // Calling super.updateShape will just call LiquidBlock's updateShape, not what we are looking for! Thankfully, Block.updateShape, simply enough, just returns the first parameter. } @Override public void neighborChanged(BlockState p_54709_, Level p_54710_, BlockPos p_54711_, Block p_54712_, BlockPos p_54713_, boolean p_54714_) { if (this.shouldSpreadLiquid(p_54710_, p_54711_, p_54709_)) { p_54710_.getLiquidTicks().scheduleTick(p_54711_, p_54709_.getFluidState().getType(), getFluid().getTickDelay(p_54710_)); } } private boolean shouldSpreadLiquid(Level p_54697_, BlockPos p_54698_, BlockState p_54699_) { if (getFluid().is(FluidTags.LAVA)) { boolean flag = p_54697_.getBlockState(p_54698_.below()).is(Blocks.SOUL_SOIL); for(Direction direction : POSSIBLE_FLOW_DIRECTIONS) { BlockPos blockpos = p_54698_.relative(direction.getOpposite()); if (p_54697_.getFluidState(blockpos).is(FluidTags.WATER)) { Block block = p_54697_.getFluidState(p_54698_).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; p_54697_.setBlockAndUpdate(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, block.defaultBlockState())); this.fizz(p_54697_, p_54698_); return false; } if (flag && p_54697_.getBlockState(blockpos).is(Blocks.BLUE_ICE)) { p_54697_.setBlockAndUpdate(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, Blocks.BASALT.defaultBlockState())); this.fizz(p_54697_, p_54698_); return false; } } } return true; } private void fizz(LevelAccessor p_54701_, BlockPos p_54702_) { p_54701_.levelEvent(1501, p_54702_, 0); } @Override public ItemStack pickupBlock(LevelAccessor p_153772_, BlockPos p_153773_, BlockState p_153774_) { if (p_153774_.getValue(LEVEL) == 0) { p_153772_.setBlock(p_153773_, Blocks.AIR.defaultBlockState(), 11); return new ItemStack(getFluid().getBucket()); } else { return ItemStack.EMPTY; } } @Override public Optional<SoundEvent> getPickupSound() { return getFluid().getPickupSound(); } } The code for my OilFluid can be found above.
    • Whoops! Sorry, my fault. But yes, a block entity (tile entity in 1.16-) *is* needed for storing data (i.e. furnace). But I think what OP is doing is making an item combiner, which doesn't need one
    • Looking to work with reliable Fabric & Forge developers to produce original mods for use on my YouTube channel! We'll be regularly working together and you'll be given a detailed brief for each commission that outlines exactly what I want. From there, you can give me a price that fits the scope of the project and we can get to work :thumbsup: Because these commissions are only for YouTube videos, you're essentially making vertical-slices / proof-of-concepts. They can have bugs, you can take shortcuts & we can use video editing to hide bugs & achieve some effects. With that in mind, turnaround speed depends on project complexity but for an average commission I'd expect delivery two days - five days Send me a email at either lmaololtbhhonest@gmail.com OR Business@tbhhonest.com if you're interested! preview of channel: https://imgur.com/a/bOVkiUo LINK:  https://www.youtube.com/channel/UCnwo3X3eCrUp7N1DZPc-MGQ  
    • If you want a tiny fraction of offset, use float.epsilon. 0.01 will actually be noticeable. It's about 1/6th of a texture pixel.
  • Topics

  • Who's Online (See full list)

  • Create New...

Important Information

By using this site, you agree to our Privacy Policy.