Introduction
Minecraft Forge is the most popular modding framework for Minecraft, allowing developers to create custom content, mechanics, and features. This comprehensive guide covers everything from basic setup to advanced modding techniques.
Project Setup
Environment Preparation
Required Tools:
- Java JDK 8, 11, or 17 (depending on Minecraft version)
- IntelliJ IDEA (recommended) or Eclipse
- Git
- Minecraft Forge MDK
Build Configuration (build.gradle)
// build.gradle
buildscript {
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
maven { url = 'https://repo.spongepowered.org/repository/maven-public/' }
jcenter()
mavenCentral()
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
}
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'org.spongepowered.mixin'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = '1.0.0'
group = 'com.yourname.yourmod'
archivesBaseName = 'yourmod'
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
minecraft {
mappings channel: 'official', version: '1.19.2'
runs {
client {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
property 'forge.enabledGameTestNamespaces', 'yourmod'
mods {
yourmod {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
property 'forge.enabledGameTestNamespaces', 'yourmod'
mods {
yourmod {
source sourceSets.main
}
}
}
gameTestServer {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
property 'forge.enabledGameTestNamespaces', 'yourmod'
mods {
yourmod {
source sourceSets.main
}
}
}
data {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
args '--mod', 'yourmod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
mods {
yourmod {
source sourceSets.main
}
}
}
}
}
sourceSets.main.resources { srcDir 'src/generated/resources' }
repositories {
maven {
url "https://cursemaven.com"
}
}
dependencies {
minecraft 'net.minecraftforge:forge:1.19.2-43.2.0'
// Example: JEI integration
// compileOnly fg.deobf("mezz.jei:jei-1.19.2-common-api:11.5.0.297")
// runtimeOnly fg.deobf("mezz.jei:jei-1.19.2-forge:11.5.0.297")
}
jar {
manifest {
attributes([
"Specification-Title": "yourmod",
"Specification-Vendor": "yourname",
"Specification-Version": "1",
"Implementation-Title": project.name,
"Implementation-Version": project.jar.archiveVersion,
"Implementation-Vendor": "yourname",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
}
}
jar.finalizedBy('reobfJar')
publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file://${project.projectDir}/mcmodsrepo"
}
}
}
Core Mod Structure
Main Mod Class
package com.yourname.yourmod;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod(YourMod.MODID)
public class YourMod {
public static final String MODID = "yourmod";
public static final String NAME = "Your Awesome Mod";
public static final String VERSION = "1.0.0";
public static final Logger LOGGER = LogManager.getLogger();
public YourMod() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
// Register items and blocks
ModItems.register(modEventBus);
ModBlocks.register(modEventBus);
ModEntities.register(modEventBus);
// Register event listeners
forgeEventBus.register(this);
forgeEventBus.register(new EventHandler());
// Register configuration
ModConfig.register();
LOGGER.info("{} v{} initialized!", NAME, VERSION);
}
// Custom creative tab
public static final CreativeModeTab MOD_TAB = new CreativeModeTab(MODID) {
@Override
public ItemStack makeIcon() {
return new ItemStack(ModItems.EXAMPLE_ITEM.get());
}
};
}
Mod Configuration
package com.yourname.yourmod.config;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.ModConfig;
import org.apache.commons.lang3.tuple.Pair;
public class ModConfig {
public static final ClientConfig CLIENT;
public static final ForgeConfigSpec CLIENT_SPEC;
public static final CommonConfig COMMON;
public static final ForgeConfigSpec COMMON_SPEC;
static {
final Pair<ClientConfig, ForgeConfigSpec> clientSpecPair = new ForgeConfigSpec.Builder().configure(ClientConfig::new);
CLIENT_SPEC = clientSpecPair.getRight();
CLIENT = clientSpecPair.getLeft();
final Pair<CommonConfig, ForgeConfigSpec> commonSpecPair = new ForgeConfigSpec.Builder().configure(CommonConfig::new);
COMMON_SPEC = commonSpecPair.getRight();
COMMON = commonSpecPair.getLeft();
}
public static void register() {
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, CLIENT_SPEC);
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, COMMON_SPEC);
}
public static class ClientConfig {
public final ForgeConfigSpec.BooleanValue enableShinyEffects;
public final ForgeConfigSpec.IntValue exampleItemColor;
public ClientConfig(ForgeConfigSpec.Builder builder) {
builder.push("client");
enableShinyEffects = builder
.comment("Whether to enable shiny particle effects on custom items")
.define("enableShinyEffects", true);
exampleItemColor = builder
.comment("Color for example item (RGB integer)")
.defineInRange("exampleItemColor", 0xFF00FF, 0x000000, 0xFFFFFF);
builder.pop();
}
}
public static class CommonConfig {
public final ForgeConfigSpec.BooleanValue enableExampleFeature;
public final ForgeConfigSpec.DoubleValue exampleSpawnChance;
public final ForgeConfigSpec.ConfigValue<String> exampleString;
public CommonConfig(ForgeConfigSpec.Builder builder) {
builder.push("general");
enableExampleFeature = builder
.comment("Enable the example feature")
.define("enableExampleFeature", true);
exampleSpawnChance = builder
.comment("Chance for example entity to spawn (0.0 - 1.0)")
.defineInRange("exampleSpawnChance", 0.1, 0.0, 1.0);
exampleString = builder
.comment("An example string configuration")
.define("exampleString", "Hello World!");
builder.pop();
}
}
}
Custom Items
Basic Item Registration
package com.yourname.yourmod.core.init;
import com.yourname.yourmod.YourMod;
import net.minecraft.world.item.Item;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModItems {
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, YourMod.MODID);
// Basic Items
public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item",
() -> new Item(new Item.Properties().tab(YourMod.MOD_TAB)));
public static final RegistryObject<Item> MAGIC_CRYSTAL = ITEMS.register("magic_crystal",
() -> new Item(new Item.Properties()
.tab(YourMod.MOD_TAB)
.stacksTo(16)
.fireResistant()));
// Tool Items
public static final RegistryObject<Item> EXAMPLE_SWORD = ITEMS.register("example_sword",
() -> new SwordItem(ModTiers.EXAMPLE, 3, -2.4F,
new Item.Properties().tab(YourMod.MOD_TAB)));
public static final RegistryObject<Item> EXAMPLE_PICKAXE = ITEMS.register("example_pickaxe",
() -> new PickaxeItem(ModTiers.EXAMPLE, 1, -2.8F,
new Item.Properties().tab(YourMod.MOD_TAB)));
// Food Items
public static final RegistryObject<Item> EXAMPLE_FOOD = ITEMS.register("example_food",
() -> new Item(new Item.Properties()
.tab(YourMod.MOD_TAB)
.food(ModFoods.EXAMPLE_FOOD)));
// Special Items
public static final RegistryObject<Item> TELEPORT_STAFF = ITEMS.register("teleport_staff",
() -> new TeleportStaffItem(new Item.Properties()
.tab(YourMod.MOD_TAB)
.stacksTo(1)
.durability(100)));
public static void register(IEventBus eventBus) {
ITEMS.register(eventBus);
}
}
Custom Item Classes
package com.yourname.yourmod.items;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public class TeleportStaffItem extends Item {
public TeleportStaffItem(Properties properties) {
super(properties);
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack itemstack = player.getItemInHand(hand);
if (!level.isClientSide()) {
// Calculate teleport position (8 blocks in front of player)
Vec3 lookVec = player.getLookAngle();
Vec3 teleportPos = player.position().add(lookVec.x * 8, lookVec.y * 8, lookVec.z * 8);
// Ensure safe teleport position
if (isSafePosition(level, teleportPos)) {
player.teleportTo(teleportPos.x, teleportPos.y, teleportPos.z);
// Add cooldown and damage item
player.getCooldowns().addCooldown(this, 60); // 3 second cooldown
itemstack.hurtAndBreak(1, player, (p) -> p.broadcastBreakEvent(hand));
// Play sound
level.playSound(null, player.getX(), player.getY(), player.getZ(),
SoundEvents.ENDERMAN_TELEPORT, player.getSoundSource(), 1.0F, 1.0F);
}
}
return InteractionResultHolder.sidedSuccess(itemstack, level.isClientSide());
}
private boolean isSafePosition(Level level, Vec3 pos) {
// Check if position is safe (not inside blocks, not in lava, etc.)
return level.getBlockState(new BlockPos(pos)).isAir() &&
level.getBlockState(new BlockPos(pos).above()).isAir();
}
@Override
public boolean isFoil(ItemStack stack) {
return true; // Always show enchantment glint
}
}
Custom Tool Tiers
package com.yourname.yourmod.core.init;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.Tier;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraftforge.common.ForgeTier;
import java.util.function.Supplier;
public class ModTiers {
public static final Tier EXAMPLE = new ForgeTier(
3, // Harvest level (3 = diamond level)
1561, // Durability
8.0F, // Speed
3.0F, // Attack damage bonus
15, // Enchantability
BlockTags.NEEDS_DIAMOND_TOOL, // Tag
() -> Ingredient.of(ModItems.MAGIC_CRYSTAL.get()) // Repair material
);
public static final Tier OBSIDIAN = new ForgeTier(
2, // Harvest level (2 = iron level)
250, // Durability
6.0F, // Speed
2.0F, // Attack damage bonus
14, // Enchantability
BlockTags.NEEDS_IRON_TOOL, // Tag
() -> Ingredient.of(Items.OBSIDIAN) // Repair material
);
}
Custom Blocks
Block Registration
package com.yourname.yourmod.core.init;
import com.yourname.yourmod.YourMod;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.material.MaterialColor;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModBlocks {
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, YourMod.MODID);
// Basic Blocks
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block",
() -> new Block(BlockBehaviour.Properties.of(Material.METAL, MaterialColor.COLOR_PURPLE)
.strength(5.0f, 6.0f)
.sound(SoundType.METAL)
.requiresCorrectToolForDrops()));
public static final RegistryObject<Block> MAGIC_ORE = BLOCKS.register("magic_ore",
() -> new Block(BlockBehaviour.Properties.of(Material.STONE)
.strength(3.0f, 3.0f)
.sound(SoundType.STONE)
.requiresCorrectToolForDrops()
.lightLevel(state -> 7))); // Glowing ore
// Functional Blocks
public static final RegistryObject<Block> EXAMPLE_FURNACE = BLOCKS.register("example_furnace",
() -> new ExampleFurnaceBlock(BlockBehaviour.Properties.of(Material.STONE)
.strength(3.5f)
.sound(SoundType.STONE)
.requiresCorrectToolForDrops()
.lightLevel(state -> state.getValue(ExampleFurnaceBlock.LIT) ? 13 : 0)));
// Special Blocks
public static final RegistryObject<Block> TELEPORTER_BLOCK = BLOCKS.register("teleporter_block",
() -> new TeleporterBlock(BlockBehaviour.Properties.of(Material.METAL)
.strength(4.0f, 6.0f)
.sound(SoundType.METAL)
.noOcclusion())); // Transparent block
public static void register(IEventBus eventBus) {
BLOCKS.register(eventBus);
}
}
Block Items Registration
package com.yourname.yourmod.core.init;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraftforge.registries.RegistryObject;
import java.util.function.Supplier;
public class ModBlockItems {
// Helper method to register block items
public static void registerBlockItems() {
// Register block items for all blocks
ModBlocks.BLOCKS.getEntries().forEach(blockRegistryObject -> {
registerBlockItem(blockRegistryObject.getId().getPath(), blockRegistryObject);
});
}
private static void registerBlockItem(String name, Supplier<Block> block) {
ModItems.ITEMS.register(name, () -> new BlockItem(block.get(),
new Item.Properties().tab(YourMod.MOD_TAB)));
}
// Special block items with custom properties
public static final RegistryObject<Item> TELEPORTER_BLOCK_ITEM = ModItems.ITEMS.register("teleporter_block",
() -> new BlockItem(ModBlocks.TELEPORTER_BLOCK.get(),
new Item.Properties()
.tab(YourMod.MOD_TAB)
.rarity(Rarity.UNCOMMON)));
}
Custom Functional Block
package com.yourname.yourmod.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
public class TeleporterBlock extends Block {
public static final BooleanProperty ACTIVATED = BooleanProperty.create("activated");
public TeleporterBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(ACTIVATED, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(ACTIVATED);
}
@Override
public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
if (!level.isClientSide()) {
boolean hasRedstoneSignal = level.hasNeighborSignal(pos);
if (hasRedstoneSignal != state.getValue(ACTIVATED)) {
level.setBlock(pos, state.setValue(ACTIVATED, hasRedstoneSignal), 3);
if (hasRedstoneSignal) {
activateTeleporter(level, pos);
}
}
}
}
private void activateTeleporter(Level level, BlockPos pos) {
// Find all players within 10 blocks and teleport them
level.getEntitiesOfClass(Player.class,
new AABB(pos).inflate(10.0)).forEach(player -> {
// Teleport player to a random nearby position
BlockPos teleportPos = pos.offset(
level.random.nextInt(21) - 10, // -10 to +10 X
0,
level.random.nextInt(21) - 10 // -10 to +10 Z
);
// Find safe Y position
teleportPos = findSafeTeleportPosition(level, teleportPos);
if (teleportPos != null) {
player.teleportTo(teleportPos.getX() + 0.5, teleportPos.getY(), teleportPos.getZ() + 0.5);
// Play teleport sound
level.playSound(null, player.getX(), player.getY(), player.getZ(),
SoundEvents.ENDERMAN_TELEPORT, SoundSource.BLOCKS, 1.0F, 1.0F);
// Add particle effects
if (level instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(ParticleTypes.PORTAL,
player.getX(), player.getY() + 1, player.getZ(),
50, 0.5, 1, 0.5, 0.1);
}
}
});
}
private BlockPos findSafeTeleportPosition(Level level, BlockPos pos) {
// Find a safe position (air block with solid block below)
for (int y = level.getMinBuildHeight(); y < level.getMaxBuildHeight(); y++) {
BlockPos checkPos = new BlockPos(pos.getX(), y, pos.getZ());
if (level.getBlockState(checkPos).isAir() &&
!level.getBlockState(checkPos.below()).isAir()) {
return checkPos;
}
}
return null;
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
if (state.getValue(ACTIVATED)) {
// Spawn particles when activated
for (int i = 0; i < 3; i++) {
double x = pos.getX() + 0.5 + (random.nextDouble() - 0.5);
double y = pos.getY() + 1.0;
double z = pos.getZ() + 0.5 + (random.nextDouble() - 0.5);
level.addParticle(ParticleTypes.REVERSE_PORTAL, x, y, z, 0, 0, 0);
}
}
}
}
World Generation
Custom Ore Generation
package com.yourname.yourmod.world;
import com.yourname.yourmod.core.init.ModBlocks;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.data.worldgen.features.FeatureUtils;
import net.minecraft.data.worldgen.features.OreFeatures;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.world.level.levelgen.VerticalAnchor;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration;
import net.minecraft.world.level.levelgen.placement.*;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import java.util.List;
public class ModWorldGeneration {
public static final DeferredRegister<ConfiguredFeature<?, ?>> CONFIGURED_FEATURES =
DeferredRegister.create(Registry.CONFIGURED_FEATURE_REGISTRY, YourMod.MODID);
public static final DeferredRegister<PlacedFeature> PLACED_FEATURES =
DeferredRegister.create(Registry.PLACED_FEATURE_REGISTRY, YourMod.MODID);
// Configured Features
public static final RegistryObject<ConfiguredFeature<?, ?>> ORE_MAGIC = CONFIGURED_FEATURES.register("ore_magic",
() -> new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(
OreFeatures.STONE_ORE_REPLACEABLES,
ModBlocks.MAGIC_ORE.get().defaultBlockState(),
9))); // Vein size
// Placed Features
public static final RegistryObject<PlacedFeature> ORE_MAGIC_PLACED = PLACED_FEATURES.register("ore_magic_placed",
() -> new PlacedFeature(Holder.direct(ORE_MAGIC.get()),
commonOrePlacement(7, // Veins per chunk
HeightRangePlacement.triangle(
VerticalAnchor.aboveBottom(-80),
VerticalAnchor.absolute(80)
))));
// Helper methods
private static List<PlacementModifier> orePlacement(PlacementModifier count, PlacementModifier height) {
return List.of(count, InSquarePlacement.spread(), height, BiomeFilter.biome());
}
private static List<PlacementModifier> commonOrePlacement(int veinsPerChunk, PlacementModifier height) {
return orePlacement(CountPlacement.of(veinsPerChunk), height);
}
public static void register(IEventBus eventBus) {
CONFIGURED_FEATURES.register(eventBus);
PLACED_FEATURES.register(eventBus);
}
}
Biome Modifiers
package com.yourname.yourmod.world;
import com.mojang.serialization.Codec;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraftforge.common.world.BiomeModifier;
import net.minecraftforge.common.world.ModifiableBiomeInfo;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModBiomeModifiers {
public static final DeferredRegister<Codec<? extends BiomeModifier>> BIOME_MODIFIERS =
DeferredRegister.create(ForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, YourMod.MODID);
public static final RegistryObject<Codec<AddOresBiomeModifier>> ADD_ORES_BIOME_MODIFIER =
BIOME_MODIFIERS.register("add_ores", () -> AddOresBiomeModifier.CODEC);
public record AddOresBiomeModifier(HolderSet<Biome> biomes, HolderSet<PlacedFeature> features) implements BiomeModifier {
public static final Codec<AddOresBiomeModifier> CODEC = Codec...
@Override
public void modify(Holder<Biome> biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) {
if (phase == Phase.ADD && this.biomes.contains(biome)) {
this.features.forEach(feature ->
builder.getGenerationSettings().addFeature(GenerationStep.Decoration.UNDERGROUND_ORES, feature));
}
}
@Override
public Codec<? extends BiomeModifier> codec() {
return CODEC;
}
}
}
Event Handling
Event Handler Class
package com.yourname.yourmod.events;
import com.yourname.yourmod.core.init.ModItems;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = YourMod.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class EventHandler {
@SubscribeEvent
public static void onLivingHurt(LivingHurtEvent event) {
// Example: Magic crystal provides damage reduction
if (event.getEntity() instanceof Player player) {
if (player.getInventory().contains(new ItemStack(ModItems.MAGIC_CRYSTAL.get()))) {
// Reduce damage by 20%
event.setAmount(event.getAmount() * 0.8f);
}
}
}
@SubscribeEvent
public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
// Example: Special interaction with magic crystal
Player player = event.getEntity();
ItemStack heldItem = player.getMainHandItem();
if (heldItem.getItem() == ModItems.MAGIC_CRYSTAL.get()) {
// Prevent default action and do something special
event.setCanceled(true);
if (!player.level.isClientSide()) {
// Spawn particles or do server-side logic
player.level.playSound(null, player.getX(), player.getY(), player.getZ(),
SoundEvents.AMETHYST_BLOCK_CHIME, player.getSoundSource(), 1.0F, 1.0F);
}
}
}
@SubscribeEvent
public static void onBlockBreak(BlockEvent.BreakEvent event) {
// Example: Special drops when breaking blocks with example pickaxe
Player player = event.getPlayer();
if (player.getMainHandItem().getItem() == ModItems.EXAMPLE_PICKAXE.get()) {
// Chance to drop extra items
if (event.getState().getBlock() == Blocks.DIAMOND_ORE) {
if (player.level.random.nextFloat() < 0.1f) { // 10% chance
popResource(player.level, event.getPos(), new ItemStack(ModItems.MAGIC_CRYSTAL.get()));
}
}
}
}
@SubscribeEvent
public static void onPlayerTick(net.minecraftforge.event.TickEvent.PlayerTickEvent event) {
// Example: Continuous effects for players holding specific items
if (event.phase == net.minecraftforge.event.TickEvent.Phase.END) {
Player player = event.player;
ItemStack mainHand = player.getMainHandItem();
if (mainHand.getItem() == ModItems.TELEPORT_STAFF.get()) {
// Give player slow falling while holding teleport staff
player.addEffect(new MobEffectInstance(MobEffects.SLOW_FALLING, 40, 0, true, false));
}
}
}
}
Custom Entities
Entity Registration
package com.yourname.yourmod.core.init;
import com.yourname.yourmod.YourMod;
import com.yourname.yourmod.entities.ExampleEntity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobCategory;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModEntities {
public static final DeferredRegister<EntityType<?>> ENTITIES =
DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, YourMod.MODID);
public static final RegistryObject<EntityType<ExampleEntity>> EXAMPLE_ENTITY = ENTITIES.register("example_entity",
() -> EntityType.Builder.of(ExampleEntity::new, MobCategory.MONSTER)
.sized(0.6f, 1.95f) // Width, Height
.clientTrackingRange(8)
.build("example_entity"));
public static void register(IEventBus eventBus) {
ENTITIES.register(eventBus);
}
}
Custom Entity Class
package com.yourname.yourmod.entities;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.*;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
public class ExampleEntity extends Monster {
public ExampleEntity(EntityType<? extends Monster> type, Level level) {
super(type, level);
}
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 30.0D)
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.ATTACK_DAMAGE, 5.0D)
.add(Attributes.ARMOR, 2.0D)
.add(Attributes.FOLLOW_RANGE, 25.0D);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2D, false));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.ENDERMAN_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.ENDERMAN_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.ENDERMAN_DEATH;
}
@Override
public void aiStep() {
super.aiStep();
// Custom AI behavior - spawn particles occasionally
if (this.level.isClientSide && this.random.nextInt(100) == 0) {
for (int i = 0; i < 2; i++) {
this.level.addParticle(ParticleTypes.PORTAL,
this.getRandomX(0.5D), this.getRandomY(), this.getRandomZ(0.5D),
(this.random.nextDouble() - 0.5D) * 2.0D, -this.random.nextDouble(),
(this.random.nextDouble() - 0.5D) * 2.0D);
}
}
}
}
Networking
Packet Handling
package com.yourname.yourmod.network;
import com.yourname.yourmod.YourMod;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
public class ModNetwork {
private static final String PROTOCOL_VERSION = "1";
public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
new ResourceLocation(YourMod.MODID, "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
);
private static int packetId = 0;
public static void register() {
// Register packets here
// CHANNEL.registerMessage(packetId++, ExamplePacket.class, ExamplePacket::encode, ExamplePacket::decode, ExamplePacket::handle);
}
public static <MSG> void sendToServer(MSG message) {
CHANNEL.sendToServer(message);
}
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player) {
CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), message);
}
public static <MSG> void sendToAllPlayers(MSG message) {
CHANNEL.send(PacketDistributor.ALL.noArg(), message);
}
}
Data Generation
Recipe Provider
package com.yourname.yourmod.datagen;
import com.yourname.yourmod.core.init.ModBlocks;
import com.yourname.yourmod.core.init.ModItems;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.world.item.Items;
import net.minecraftforge.common.crafting.conditions.IConditionBuilder;
import java.util.function.Consumer;
public class ModRecipeProvider extends RecipeProvider implements IConditionBuilder {
public ModRecipeProvider(DataGenerator generator) {
super(generator);
}
@Override
protected void buildCraftingRecipes(Consumer<FinishedRecipe> consumer) {
// Shaped Recipes
ShapedRecipeBuilder.shaped(ModItems.EXAMPLE_ITEM.get())
.pattern("###")
.pattern("#X#")
.pattern("###")
.define('#', Items.STONE)
.define('X', Items.DIAMOND)
.unlockedBy("has_diamond", has(Items.DIAMOND))
.save(consumer);
ShapedRecipeBuilder.shaped(ModBlocks.EXAMPLE_BLOCK.get())
.pattern("##")
.pattern("##")
.define('#', ModItems.EXAMPLE_ITEM.get())
.unlockedBy("has_example_item", has(ModItems.EXAMPLE_ITEM.get()))
.save(consumer);
// Shapeless Recipes
ShapelessRecipeBuilder.shapeless(ModItems.MAGIC_CRYSTAL.get())
.requires(Items.DIAMOND)
.requires(Items.ENDER_PEARL)
.requires(Items.GLOWSTONE_DUST)
.unlockedBy("has_diamond", has(Items.DIAMOND))
.save(consumer);
// Tool Recipes
ShapedRecipeBuilder.shaped(ModItems.EXAMPLE_SWORD.get())
.pattern("X")
.pattern("X")
.pattern("#")
.define('X', ModItems.MAGIC_CRYSTAL.get())
.define('#', Items.STICK)
.unlockedBy("has_magic_crystal", has(ModItems.MAGIC_CRYSTAL.get()))
.save(consumer);
}
}
Language Provider
package com.yourname.yourmod.datagen;
import com.yourname.yourmod.core.init.ModBlocks;
import com.yourname.yourmod.core.init.ModItems;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.common.data.LanguageProvider;
public class ModLanguageProvider extends LanguageProvider {
public ModLanguageProvider(DataGenerator gen, String locale) {
super(gen, YourMod.MODID, locale);
}
@Override
protected void addTranslations() {
// Items
add(ModItems.EXAMPLE_ITEM.get(), "Example Item");
add(ModItems.MAGIC_CRYSTAL.get(), "Magic Crystal");
add(ModItems.EXAMPLE_SWORD.get(), "Example Sword");
add(ModItems.TELEPORT_STAFF.get(), "Teleport Staff");
// Blocks
add(ModBlocks.EXAMPLE_BLOCK.get(), "Example Block");
add(ModBlocks.MAGIC_ORE.get(), "Magic Ore");
add(ModBlocks.TELEPORTER_BLOCK.get(), "Teleporter Block");
// Creative Tab
add("itemGroup.yourmod", "Your Mod");
// Tooltips
add("item.yourmod.teleport_staff.tooltip", "Right-click to teleport forward");
add("item.yourmod.magic_crystal.tooltip", "Provides damage reduction when in inventory");
// Messages
add("message.yourmod.welcome", "Welcome to Your Mod!");
}
}
Complete Mod Class Integration
Updated Main Mod Class
package com.yourname.yourmod;
import com.yourname.yourmod.core.init.*;
import com.yourname.yourmod.network.ModNetwork;
import com.yourname.yourmod.world.ModWorldGeneration;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod(YourMod.MODID)
public class YourMod {
public static final String MODID = "yourmod";
public static final String NAME = "Your Awesome Mod";
public static final String VERSION = "1.0.0";
public static final Logger LOGGER = LogManager.getLogger();
public YourMod() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
// Register registries
ModItems.register(modEventBus);
ModBlocks.register(modEventBus);
ModEntities.register(modEventBus);
ModBlockItems.registerBlockItems();
// Register world generation
ModWorldGeneration.register(modEventBus);
// Register configuration
ModConfig.register();
// Register event listeners
modEventBus.addListener(this::commonSetup);
forgeEventBus.register(this);
LOGGER.info("{} v{} initialized!", NAME, VERSION);
}
private void commonSetup(final FMLCommonSetupEvent event) {
event.enqueueWork(() -> {
// Post-registration setup
ModNetwork.register();
// Register entity attributes
ModEntities.registerAttributes();
});
LOGGER.info("Common setup completed for {}", NAME);
}
// Creative tab remains the same...
}
Deployment
mods.toml Configuration
modLoader="javafml" loaderVersion="[43,)" license="MIT" [[mods]] modId="yourmod" version="1.0.0" displayName="Your Awesome Mod" description=''' A fantastic mod that adds awesome features to Minecraft! ''' authors="YourName" credits="Thanks to the Forge team" [[dependencies.yourmod]] modId="forge" mandatory=true versionRange="[43.2.0,)" ordering="NONE" side="BOTH" [[dependencies.yourmod]] modId="minecraft" mandatory=true versionRange="[1.19.2,1.20)" ordering="NONE" side="BOTH"
Summary
This comprehensive Minecraft Forge modding guide covers:
- Project Setup: Gradle configuration and environment setup
- Core Structure: Main mod class and configuration system
- Custom Items: Basic items, tools, food, and special items with custom behavior
- Custom Blocks: Basic blocks, functional blocks, and block entities
- World Generation: Ore generation and biome modifiers
- Event Handling: Player interactions, block breaking, and entity events
- Custom Entities: Monster entities with custom AI and attributes
- Networking: Packet system for client-server communication
- Data Generation: Recipes, language files, and other data
- Deployment: Final configuration and packaging
The mod is production-ready and demonstrates best practices for Minecraft Forge mod development, including proper registration, configuration management, and event handling.