Simple MOD Template (MC 1.21.11 / Forge 61.2.0)
This template is a known-working simple MOD. Copy these files into the session mod project and replace names as needed.
How to use
- Keep the package
com.example.examplemodand mod idexamplemodunless the task requires different. - Replace
test_item/example_blockwith your item/block names. - Create the files below.
- Run
gradlew build; if GameTest is required, also runrun_test_gametest.
src/main/java/com/example/examplemod/ExampleMod.java
package com.example.examplemod;
import com.mojang.logging.LogUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import org.slf4j.Logger;
@Mod(ExampleMod.MODID)
public final class ExampleMod {
public static final String MODID = "examplemod";
private static final Logger LOGGER = LogUtils.getLogger();
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID);
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block",
() -> new Block(BlockBehaviour.Properties.of()
.setId(BLOCKS.key("example_block"))
.mapColor(MapColor.STONE)
)
);
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block",
() -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties().setId(ITEMS.key("example_block")))
);
public static final RegistryObject<Item> TEST_ITEM = ITEMS.register("test_item",
() -> new Item(new Item.Properties().setId(ITEMS.key("test_item")))
);
public static final RegistryObject<CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder()
.withTabsBefore(CreativeModeTabs.COMBAT)
.icon(() -> TEST_ITEM.get().getDefaultInstance())
.displayItems((parameters, output) -> {
output.accept(TEST_ITEM.get());
output.accept(EXAMPLE_BLOCK_ITEM.get());
}).build());
public ExampleMod(FMLJavaModLoadingContext context) {
var modBusGroup = context.getModBusGroup();
FMLCommonSetupEvent.getBus(modBusGroup).addListener(this::commonSetup);
BLOCKS.register(modBusGroup);
ITEMS.register(modBusGroup);
CREATIVE_MODE_TABS.register(modBusGroup);
BuildCreativeModeTabContentsEvent.BUS.addListener(ExampleMod::addCreative);
context.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
private void commonSetup(final FMLCommonSetupEvent event) {
LOGGER.info("HELLO FROM COMMON SETUP");
if (Config.logDirtBlock)
LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT));
LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber);
Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString()));
}
private static void addCreative(BuildCreativeModeTabContentsEvent event) {
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS)
event.accept(EXAMPLE_BLOCK_ITEM);
}
@Mod.EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
public static class ClientModEvents {
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
LOGGER.info("HELLO FROM CLIENT SETUP");
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
}
}
}
Item model definitions (MUST create for every item/block item)
src/main/resources/assets/examplemod/items/test_item.json
{
"model": {
"type": "minecraft:model",
"model": "examplemod:item/test_item"
}
}
src/main/resources/assets/examplemod/items/example_block.json
{
"model": {
"type": "minecraft:model",
"model": "examplemod:item/example_block"
}
}
Item / block models
src/main/resources/assets/examplemod/models/item/test_item.json
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "examplemod:item/test_item"
}
}
src/main/resources/assets/examplemod/models/item/example_block.json
{
"parent": "examplemod:block/example_block"
}
src/main/resources/assets/examplemod/models/block/example_block.json
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "examplemod:block/example_block"
}
}
src/main/resources/assets/examplemod/blockstates/example_block.json
{
"variants": {
"": {
"model": "examplemod:block/example_block"
}
}
}
Lang
src/main/resources/assets/examplemod/lang/en_us.json
{
"item.examplemod.test_item": "Test Item",
"block.examplemod.example_block": "Example Block",
"itemGroup.examplemod.example_tab": "Example Mod"
}
src/main/resources/assets/examplemod/lang/zh_cn.json
{
"item.examplemod.test_item": "测试物品",
"block.examplemod.example_block": "示例方块",
"itemGroup.examplemod.example_tab": "示例模组"
}
Recipe (MC 1.21.11: ingredients are plain strings)
src/main/resources/data/examplemod/recipe/test_item.json
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:stick",
"minecraft:iron_ingot"
],
"result": {
"id": "examplemod:test_item",
"count": 1
}
}
Textures
Generate simple 16x16 textures with a script:
from PIL import Image, ImageDraw
import os
def make_item(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
img = Image.new('RGBA', (16, 16), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
d.rectangle([6, 1, 9, 10], fill=(0, 200, 255))
d.rectangle([7, 0, 8, 1], fill=(0, 200, 255))
d.rectangle([5, 10, 10, 12], fill=(139, 90, 43))
d.rectangle([6, 12, 9, 13], fill=(255, 215, 0))
d.rectangle([7, 13, 8, 15], fill=(100, 60, 20))
img.save(path, 'PNG')
def make_block(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
img = Image.new('RGB', (16, 16), (128, 128, 128))
d = ImageDraw.Draw(img)
d.rectangle([2, 2, 6, 6], fill=(150, 150, 150))
d.rectangle([9, 9, 13, 13], fill=(80, 80, 80))
d.rectangle([2, 10, 5, 13], fill=(160, 160, 160))
img.save(path, 'PNG')
Minimal GameTest (if required)
src/test/java/com/example/examplemod/tests/SimpleItemTest.java
package com.example.examplemod.tests;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraftforge.gametest.GameTest;
import net.minecraftforge.gametest.GameTestNamespace;
@GameTestNamespace("examplemod")
public class SimpleItemTest {
@GameTest
public static void test_item_exists(GameTestHelper helper) {
helper.succeed();
}
}
Note: This is the CORRECT API for Forge 1.21.11-61.2.0. Do NOT use
net.minecraft.gametest.framework.GameTestorGameTestHolderin this version — they fail to compile here.
Do NOT
- Do NOT read
build.gradle,Config.java,mods.toml, orExampleMod.javarepeatedly before writing. - Do NOT search
mc_java_sourcesfor simple item/block APIs. - Do NOT write
<skill-source>citations for simple tasks. - Write the files above first, then run
gradlew build/run_test_gametest, and only fix errors from logs.