minecraft-modding
Create, modify, debug, or migrate Minecraft mods for current NeoForge or Fabric 26.x, legacy 1.21.x, and Forge 1.20.1. Use for loader-based gameplay code and assets; use minecraft-multiloader when one codebase must target both modern loaders.
Install
npx skills add https://github.com/Jahrome907/minecraft-agent-skills/tree/main/plugins/minecraft-codex-skills/skills/minecraft-modding
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jahrome907-minecraft-agent-skills@llmmart
git clone https://github.com/Jahrome907/minecraft-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jahrome907/minecraft-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Minecraft Modding Skill
Overview
Supported platforms:
| Platform | MC Version | Java | Build System |
|---|---|---|---|
| NeoForge | 26.x current; 1.21.11 examples retained | Java 25 current; Java 21 on 1.21.x | Gradle + ModDevGradle |
| Forge | 1.20.1 legacy lane | Java 17 | Gradle + ForgeGradle 6 |
| Fabric | 26.x current; 1.21.11 examples retained | Java 25 current; Java 21 on 1.21.x | Gradle + Fabric Loom |
| Architectury (multiloader) | 26.x or 1.21.x | Match Minecraft | Gradle + Architectury Loom |
Always confirm the platform and Minecraft version from gradle.properties or build.gradle
before writing any mod-specific code.
Minecraft 26.1 introduced Java 25 and unobfuscated game executables. For 26.x projects, start from the current loader generator or example mod and preserve its build layout. Do not copy the 1.21.11 mapping, Loom plugin, remapping task, or Java 21 snippets in this skill into a 26.x project. Fabric 26.x uses the non-remapping Loom path and official names; NeoForge 26.x should start from the current NeoForge generator. The detailed API references cover legacy 1.21.x examples unless a section explicitly says 26.x.
Routing Boundaries
Use when: the task is Java/Kotlin mod code, registry/event work, networking, datagen wiring, and loader APIs.Do not use when: the task is command-only vanilla logic (minecraft-commands-scripting) or pure datapacks (minecraft-datapack).Do not use when: the task targets Paper/Bukkit plugins (minecraft-plugin-dev).
1. Identifying the Platform
# NeoForge project signature
grep -r "net.neoforged" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5
# Forge 1.20.1 project signature
grep -r "net.minecraftforge" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5
# Fabric project signature
grep -r "fabric" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5
# Read mod ID and version
cat gradle.properties
Key files per platform:
- NeoForge:
src/main/resources/META-INF/neoforge.mods.toml, annotated@Modmain class - Forge 1.20.1:
src/main/resources/META-INF/mods.toml,net.minecraftforge:forgedependency - Fabric:
src/main/resources/fabric.mod.json, class implementingModInitializer - Architectury:
common/,fabric/,neoforge/subprojects
2. Build & Test Commands
# Build the mod jar
./gradlew build
# Run the Minecraft client to test
./gradlew runClient
# Run a dedicated server to test
./gradlew runServer
# Run game tests (NeoForge JUnit-style game tests)
./gradlew runGameTestServer
# Run data generation (generates JSON assets automatically)
./gradlew runData
# Remove this project's generated build outputs before a fresh rebuild
./gradlew clean
# Check for dependency updates (optional)
./gradlew dependencyUpdates
./gradlew build runs the project's configured build tasks. Candidate mod jars are
usually under build/libs/, but task names and file names are project-specific.
Check the build result, then identify the intended
distributable before publishing it.
3. Project Layout (NeoForge)
src/
main/
java/<groupId>/<modid>/
MyMod.java ← @Mod entry point
block/
ModBlocks.java ← DeferredRegister.Blocks
MyCustomBlock.java
item/
ModItems.java ← DeferredRegister.Items
entity/
ModEntities.java ← DeferredRegister.Entities
menu/ ← custom GUI containers
recipe/
worldgen/
datagen/
ModDataGen.java ← GatherDataEvent handler
providers/
resources/
META-INF/
neoforge.mods.toml ← mod metadata (renamed from mods.toml in NeoForge 1.20.5+)
assets/<modid>/
blockstates/ ← JSON blockstate definitions
models/
block/ ← block model JSON
item/ ← item model JSON
items/ ← 1.21.x item-definition JSON
textures/
block/ ← 16×16 PNG textures
item/
lang/
en_us.json ← translation strings
data/<modid>/
recipe/ ← crafting recipe JSON (26.x)
loot_table/
blocks/ ← per-block loot table JSON
tags/
blocks/
items/
4. Project Layout (Forge 1.20.1)
Use this layout only when minecraft_version=1.20.1 and the project depends on
net.minecraftforge:forge. Forge 1.20.1 is not NeoForge: keep mods.toml,
net.minecraftforge.* imports, Java 17, and ForgeGradle 6 patterns.
src/
main/
java/<groupId>/<modid>/
MyMod.java <- @Mod entry point
block/
ModBlocks.java <- DeferredRegister.Blocks
item/
ModItems.java <- DeferredRegister.Items
datagen/
ModDataGen.java <- GatherDataEvent handler
resources/
META-INF/
mods.toml <- Forge metadata
assets/<modid>/ <- client assets
data/<modid>/ <- server data using 1.20.1 paths
See references/forge-1.20.1-api.md before editing Forge 1.20.1 projects.
5. Project Layout (Fabric)
src/
main/
java/<groupId>/<modid>/
MyMod.java ← implements ModInitializer
client/
MyModClient.java ← implements ClientModInitializer
block/
item/
mixin/ ← Mixin classes
resources/
fabric.mod.json
assets/<modid>/ ← same as NeoForge
data/<modid>/ ← same as NeoForge
<modid>.mixins.json ← mixin configuration
6. Core Concepts Cheatsheet
Sides
- Physical client – the game client JAR (has rendering code)
- Physical server – the dedicated server JAR (no rendering)
- Logical client – the client thread (handles rendering, input)
- Logical server – the server thread (handles world simulation)
- Code decorated with
@OnlyIn(Dist.CLIENT)(NeoForge) or@Environment(EnvType.CLIENT)(Fabric) must NEVER run on the server.
Registries
Everything in Minecraft lives in a registry. Always register objects; never construct them at field initializer time outside a registry call. Use the mapping-appropriate registry constants for the loader you are editing:
| Type | NeoForge / Mojang mappings | Fabric / Yarn mappings |
|---|---|---|
| Blocks | BuiltInRegistries.BLOCK |
Registries.BLOCK |
| Items | BuiltInRegistries.ITEM |
Registries.ITEM |
| Entity types | BuiltInRegistries.ENTITY_TYPE |
Registries.ENTITY_TYPE |
| Block entity types | BuiltInRegistries.BLOCK_ENTITY_TYPE |
Registries.BLOCK_ENTITY_TYPE |
| Menu / screen-handler types | BuiltInRegistries.MENU |
Registries.SCREEN_HANDLER |
| Sound events | BuiltInRegistries.SOUND_EVENT |
Registries.SOUND_EVENT |
| Biomes | Registries.BIOME registry keys |
RegistryKeys.BIOME registry keys |
Do not copy older Registry.BLOCK / Registry.ITEM constants into 1.21.x code;
those names are stale for the examples in this skill.
ResourceLocation / Identifier
Every registry entry needs a namespaced ID:
// NeoForge / vanilla Java
ResourceLocation id = ResourceLocation.fromNamespaceAndPath("mymod", "my_block");
// Fabric with Yarn mappings
Identifier id = Identifier.of("mymod", "my_block");
7. NeoForge Quick Patterns (26.x)
For 26.x, use the explicitly labelled 26.x sections in
references/common-patterns.md and select the project's exact version in the
NeoForge documentation.
references/neoforge-api.md contains legacy 1.21.x / Java 21 patterns only;
do not copy its dependency pins into a 26.x project.
// Main mod class
@Mod(MyMod.MOD_ID)
public class MyMod {
public static final String MOD_ID = "mymod";
public MyMod(IEventBus modEventBus) {
ModBlocks.BLOCKS.register(modEventBus);
ModItems.ITEMS.register(modEventBus);
modEventBus.addListener(this::commonSetup);
}
private void commonSetup(FMLCommonSetupEvent event) {
// runs after all mods are registered
}
}
// Block registration
public class ModBlocks {
public static final DeferredRegister.Blocks BLOCKS =
DeferredRegister.createBlocks(MyMod.MOD_ID);
public static final DeferredBlock<Block> MY_BLOCK =
BLOCKS.registerSimpleBlock("my_block",
BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(1.5f, 6.0f)
.sound(SoundType.STONE)
.requiresCorrectToolForDrops());
}
8. Forge 1.20.1 Quick Patterns
See full patterns in references/forge-1.20.1-api.md.
// Main mod class
@Mod(MyMod.MOD_ID)
public class MyMod {
public static final String MOD_ID = "mymod";
public MyMod(FMLJavaModLoadingContext context) {
IEventBus modEventBus = context.getModEventBus();
ModBlocks.BLOCKS.register(modEventBus);
ModItems.ITEMS.register(modEventBus);
modEventBus.addListener(this::commonSetup);
MinecraftForge.EVENT_BUS.register(this);
}
private void commonSetup(FMLCommonSetupEvent event) {
// runs after registries are prepared
}
}
// Block registration
public class ModBlocks {
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, MyMod.MOD_ID);
public static final RegistryObject<Block> MY_BLOCK =
BLOCKS.register("my_block", () -> new Block(
BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(1.5f, 6.0f)
.sound(SoundType.STONE)
.requiresCorrectToolForDrops()));
}
9. Fabric Quick Patterns
Match the project's Minecraft version and mappings in the
Fabric documentation.
references/fabric-api.md contains legacy 1.21.x / Java 21 patterns only.
The explicitly labelled 26.x sections in references/common-patterns.md
use NeoForge syntax; adapt them against the exact Fabric API rather than
copying loader-specific classes or legacy dependency pins.
// Main mod class
public class MyMod implements ModInitializer {
public static final String MOD_ID = "mymod";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
ModBlocks.initialize();
ModItems.register();
}
}
// Fabric 26.x with official Mojang mappings. Create the key before the object
// so its properties receive the required id during construction.
public final class ModBlocks {
public static final ResourceKey<Block> MY_BLOCK_KEY = ResourceKey.create(
Registries.BLOCK,
Identifier.fromNamespaceAndPath(MyMod.MOD_ID, "my_block")
);
public static final Block MY_BLOCK = register(
MY_BLOCK_KEY,
Block::new,
BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)
.strength(1.5f, 6.0f)
.sound(SoundType.STONE)
.requiresCorrectToolForDrops()
);
private static Block register(ResourceKey<Block> key,
Function<BlockBehaviour.Properties, Block> factory,
BlockBehaviour.Properties properties) {
Block block = factory.apply(properties.setId(key));
return Registry.register(BuiltInRegistries.BLOCK, key, block);
}
public static void initialize() {}
}
Call ModBlocks.initialize() from the Fabric initializer. The Yarn-named
1.21.11 examples remain in references/fabric-api.md; do not mix those names
with this 26.x pattern.
10. JSON Asset Templates
Always provide matching JSON assets for every registered block/item.
Codex should generate or update these files alongside Java code.
For Forge 1.20.1, check references/forge-1.20.1-api.md for legacy server-data
directory names before creating loot tables or tags.
See references/common-patterns.md for full JSON templates for:
- Blockstate JSON
- Block model JSON (cube, slab, stairs, fence, door, trapdoor, etc.)
- Item model JSON
- Loot table JSON
- Recipe JSON (crafting_shaped, crafting_shapeless, smelting, blasting, stonecutting)
- Language file (
en_us.json) entries - Tag JSON
11. Data Generation
Prefer data generation over hand-authored JSON for maintainability.
// NeoForge – register data gen providers in GatherDataEvent
@SubscribeEvent
public static void gatherData(GatherDataEvent event) {
DataGenerator gen = event.getGenerator();
PackOutput output = gen.getPackOutput();
ExistingFileHelper helper = event.getExistingFileHelper();
CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider();
gen.addProvider(event.includeClient(), new ModBlockStateProvider(output, helper));
gen.addProvider(event.includeClient(), new ModItemModelProvider(output, helper));
gen.addProvider(event.includeServer(), new ModRecipeProvider(output, lookupProvider));
gen.addProvider(event.includeServer(), new ModLootTableProvider(output, lookupProvider));
gen.addProvider(event.includeServer(), new ModBlockTagsProvider(output, lookupProvider, helper));
}
Run data generation with ./gradlew runData, then commit the generated files.
For Forge 1.20.1, use the mod-event-bus registration, GatherDataEvent
signature, provider classes, and legacy output paths from
references/forge-1.20.1-api.md.
12. Common Tasks Checklist
When adding a new block:
-
Blocksubclass (or use vanilla Block with properties) - Register in
ModBlocks.BLOCKS/Registries.BLOCK - Register
BlockIteminModItems.ITEMS/Registries.ITEM - Blockstate JSON →
assets/<modid>/blockstates/<name>.json - Block model JSON →
assets/<modid>/models/block/<name>.json - Item model JSON →
assets/<modid>/models/item/<name>.json(or inherits from block) - 1.21.x item definition →
assets/<modid>/items/<name>.json, pointing at the item or block model - Texture PNG →
assets/<modid>/textures/block/<name>.png - Loot table JSON -> 1.21.x:
data/<modid>/loot_table/blocks/<name>.json; Forge 1.20.1:data/<modid>/loot_tables/blocks/<name>.json - Tags -> 1.21.x:
data/<modid>/tags/block/andtags/item/; Forge 1.20.1:data/<modid>/tags/blocks/andtags/items/ - Language entry in
en_us.json - Mine-with-correct-tool tag if hardness > 0
- Do not mix Forge 1.20.1 plural server-data paths with 1.21.x singular server-data paths
When adding a new item:
-
Itemsubclass (or usenew Item(properties)) - Register in
ModItems/Registries.ITEM - Item model JSON
- 1.21.x item definition →
assets/<modid>/items/<name>.json - Texture PNG
- Language entry
- Creative tab registration (NeoForge/Forge:
BuildCreativeModeTabContentsEvent; Fabric:ItemGroupEvents) - Recipe JSON if craftable (
data/<modid>/recipe/for 26.x; see the version-specific recipe reference before using a 1.21.x project)
When adding a new entity:
- Entity class (extends appropriate base:
Mob,Animal,TamableAnimal, etc.) -
EntityTyperegistration - Renderer class (
@OnlyIn(Dist.CLIENT)) - Model class (
@OnlyIn(Dist.CLIENT)) - Register renderer in
EntityRenderersEvent.RegisterRenderers(NeoForge) orEntityModelLayerRegistry(Fabric) - Spawn egg item (optional)
- Spawn rules / biome modifier
13. Open-Source Conventions
- License: MIT or LGPL-3.0 — include
LICENSEfile andSPDX-License-Identifierheader - Versioning:
{mod_version}+{mc_version}(e.g.,2.0.0+1.21.11) - Changelog: Keep
CHANGELOG.mdup to date with semver notes - Publishing: Use
gradle-modrinthorcurseforgegradleplugins for CurseForge / Modrinth - CI: GitHub Actions with
./gradlew buildand./gradlew runGameTestServer - PR conventions: Keep PRs scoped to a single feature; include asset files with Java changes
14. References
- NeoForge API patterns and event system:
./references/neoforge-api.md - Forge 1.20.1 API patterns and MDK workflow:
./references/forge-1.20.1-api.md - Fabric API patterns and mixin guide:
./references/fabric-api.md - Blocks, items, recipes, commands, GUIs, datagen:
./references/common-patterns.md - NeoForge official docs: https://docs.neoforged.net/
- Fabric developer docs: https://docs.fabricmc.net/develop/
- Architectury (multiloader): https://docs.architectury.dev/
- Minecraft Wiki (data formats): https://minecraft.wiki/w/Java_Edition_data_values
Files (minecraft-agent-skills)
-
references
-
common-patterns.md 15.5 KB
# Common Minecraft Modding Patterns Cross-platform patterns for blocks, items, entities, data generation, commands, recipes, and more. Code examples use NeoForge syntax unless noted; adapt field/method names for Fabric. This reference retains 1.21.11 examples alongside 26.x replacements. A heading that names Minecraft 26.x uses the 26.1 API and official Mojang mappings; verify any later 26.x API change before copying code into a pinned project. Treat all other examples as 1.21.11 examples unless their exact API is verified for the project. --- ## Blocks ### Simple Full-Cube Block Files needed: 1. Java class (if custom behavior) or `registerSimpleBlock()` call 2. `assets/<modid>/blockstates/<name>.json` 3. `assets/<modid>/models/block/<name>.json` 4. `assets/<modid>/items/<name>.json` (1.21.x item definition) 5. `assets/<modid>/models/item/<name>.json` 6. `assets/<modid>/textures/block/<name>.png` 7. `data/<modid>/loot_table/blocks/<name>.json` 8. `en_us.json` entry `assets/mymod/blockstates/my_block.json`: ```json { "variants": { "": { "model": "mymod:block/my_block" } } } ``` `assets/mymod/models/block/my_block.json`: ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "mymod:block/my_block" } } ``` `assets/mymod/items/my_block.json`: ```json { "model": { "type": "minecraft:model", "model": "mymod:block/my_block" } } ``` `assets/mymod/models/item/my_block.json` is only needed when the block item needs a model distinct from the block model. For that case: ```json { "parent": "mymod:block/my_block" } ``` `data/mymod/loot_table/blocks/my_block.json`: ```json { "type": "minecraft:block", "pools": [{ "rolls": 1, "entries": [{ "type": "minecraft:item", "name": "mymod:my_block" }], "conditions": [{ "condition": "minecraft:survives_explosion" }] }] } ``` --- ### Directional Block (faces a direction when placed) ```java public class MyDirectionalBlock extends DirectionalBlock { public static final DirectionProperty FACING = DirectionalBlock.FACING; public MyDirectionalBlock(Properties props) { super(props); registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH)); } @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { return defaultBlockState().setValue(FACING, ctx.getNearestLookingDirection().getOpposite()); } @Override protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { builder.add(FACING); } } ``` `assets/mymod/blockstates/my_directional_block.json`: ```json { "variants": { "facing=north": { "model": "mymod:block/my_directional_block" }, "facing=south": { "model": "mymod:block/my_directional_block", "y": 180 }, "facing=west": { "model": "mymod:block/my_directional_block", "y": 270 }, "facing=east": { "model": "mymod:block/my_directional_block", "y": 90 }, "facing=up": { "model": "mymod:block/my_directional_block", "x": -90 }, "facing=down": { "model": "mymod:block/my_directional_block", "x": 90 } } } ``` --- ### Slab Block ```java public static final DeferredBlock<SlabBlock> MY_SLAB = BLOCKS.register("my_slab", () -> new SlabBlock( BlockBehaviour.Properties.ofFullCopy(Blocks.STONE_SLAB))); ``` `assets/mymod/models/block/my_slab.json`: ```json { "parent": "minecraft:block/slab", "textures": { "bottom": "mymod:block/my_block", "top": "mymod:block/my_block", "side": "mymod:block/my_block" } } ``` `assets/mymod/models/block/my_slab_top.json`: ```json { "parent": "minecraft:block/slab_top", "textures": { "bottom": "mymod:block/my_block", "top": "mymod:block/my_block", "side": "mymod:block/my_block" } } ``` `assets/mymod/blockstates/my_slab.json`: ```json { "variants": { "type=bottom": { "model": "mymod:block/my_slab" }, "type=top": { "model": "mymod:block/my_slab_top" }, "type=double": { "model": "mymod:block/my_block" } } } ``` --- ### Stairs Block ```java public static final DeferredBlock<StairBlock> MY_STAIRS = BLOCKS.register("my_stairs", () -> new StairBlock( ModBlocks.MY_BLOCK.get().defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(Blocks.STONE_STAIRS))); ``` `assets/mymod/models/block/my_stairs.json`: ```json { "parent": "minecraft:block/stairs", "textures": { "bottom": "mymod:block/my_block", "top": "mymod:block/my_block", "side": "mymod:block/my_block" } } ``` Also create `my_stairs_inner.json` and `my_stairs_outer.json`, inheriting from `minecraft:block/inner_stairs` and `minecraft:block/outer_stairs` respectively. --- ## Items ### Food Item ```java // NeoForge public static final DeferredItem<Item> MY_FOOD = ITEMS.registerSimpleItem("my_food", new Item.Properties() .food(new FoodProperties.Builder() .nutrition(4) .saturationModifier(0.3f) .effect(new MobEffectInstance(MobEffects.REGENERATION, 200, 1), 0.8f) .build())); ``` `assets/mymod/items/my_food.json`: ```json { "model": { "type": "minecraft:model", "model": "mymod:item/my_food" } } ``` `assets/mymod/models/item/my_food.json`: ```json { "parent": "minecraft:item/generated", "textures": { "layer0": "mymod:item/my_food" } } ``` ### Tool Item (NeoForge 26.x) `Tier` and `SwordItem.createAttributes` are obsolete here. Define a `ToolMaterial`, then use the `Item.Properties` tool delegate during item registration. ```java public static final ToolMaterial MY_TOOL_MATERIAL = new ToolMaterial( ModBlockTags.INCORRECT_FOR_MY_TOOL, 455, 5.0f, 1.5f, 22, ModItemTags.REPAIRS_MY_TOOL ); public static final DeferredItem<Item> MY_SWORD = ITEMS.registerItem("my_sword", props -> new Item(props.sword(MY_TOOL_MATERIAL, 3, -2.4f))); ``` ### Armor Set (NeoForge 26.x) `ArmorMaterial` is not a registry entry. Its equipment asset key identifies the corresponding equipment definition, while `humanoidArmor` applies the material to a normal `Item`. ```java public static final ResourceKey<EquipmentAsset> MY_ARMOR_ASSET = ResourceKey.create(EquipmentAssets.ROOT_ID, Identifier.fromNamespaceAndPath(MyMod.MOD_ID, "my_material")); public static final ArmorMaterial MY_ARMOR_MATERIAL = new ArmorMaterial( 15, Map.of( ArmorType.HELMET, 3, ArmorType.CHESTPLATE, 8, ArmorType.LEGGINGS, 6, ArmorType.BOOTS, 3 ), 5, SoundEvents.ARMOR_EQUIP_IRON, 0.0f, 0.0f, ModItemTags.REPAIRS_MY_ARMOR, MY_ARMOR_ASSET ); public static final DeferredItem<Item> MY_HELMET = ITEMS.registerItem("my_helmet", props -> new Item(props.humanoidArmor(MY_ARMOR_MATERIAL, ArmorType.HELMET))); ``` --- ## Entity Types ```java // ModEntityTypes.java (NeoForge 26.x) public class ModEntityTypes { public static final DeferredRegister.Entities ENTITY_TYPES = DeferredRegister.createEntities(MyMod.MOD_ID); public static final Supplier<EntityType<MyEntity>> MY_ENTITY = ENTITY_TYPES.registerEntityType( "my_entity", MyEntity::new, MobCategory.CREATURE, builder -> builder .sized(0.9f, 1.3f) .clientTrackingRange(8) .updateInterval(3) ); } ``` For a concrete entity subclass, attributes, spawning, or renderer wiring, use the current NeoForge entity guide for the project's exact version. Do not copy the removed `EntityType.Builder#build(String)` overload into 26.x code. --- ## Commands (Brigadier — works the same in NeoForge and Fabric) ```java // NeoForge — register on GAME bus @EventBusSubscriber(modid = MyMod.MOD_ID, bus = Bus.GAME) public class ModCommands { @SubscribeEvent public static void onRegisterCommands(RegisterCommandsEvent event) { registerCommands(event.getDispatcher()); } } // Shared implementation private static void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register( Commands.literal("mymod") .then(Commands.literal("give") .requires(src -> src.hasPermission(2)) // op level 2 .then(Commands.argument("player", EntityArgument.player()) .then(Commands.argument("count", IntegerArgumentType.integer(1, 64)) .executes(ctx -> executeGive(ctx, EntityArgument.getPlayer(ctx, "player"), IntegerArgumentType.getInteger(ctx, "count")))))) ); } private static int executeGive(CommandContext<CommandSourceStack> ctx, ServerPlayer player, int count) throws CommandSyntaxException { ItemStack stack = new ItemStack(ModItems.MY_ITEM.get(), count); player.getInventory().add(stack); ctx.getSource().sendSuccess( () -> Component.translatable("commands.mymod.give.success", count, player.getDisplayName()), true); return count; } ``` --- ## Recipes (Minecraft 26.x JSON) ### Shaped Crafting Recipe `data/mymod/recipe/my_item.json`: ```json { "type": "minecraft:crafting_shaped", "pattern": [ "SSS", " I ", " I " ], "key": { "S": "minecraft:stone", "I": "minecraft:iron_ingot" }, "result": { "id": "mymod:my_item", "count": 1 } } ``` ### Shapeless Recipe ```json { "type": "minecraft:crafting_shapeless", "ingredients": [ "minecraft:diamond", "minecraft:emerald" ], "result": { "id": "mymod:my_item", "count": 2 } } ``` ### Smelting / Blasting / Smoking / Campfire ```json { "type": "minecraft:smelting", "ingredient": { "item": "mymod:my_ore" }, "result": { "id": "mymod:my_ingot" }, "experience": 0.7, "cookingtime": 200 } ``` ### Custom Recipe Type (NeoForge / Fabric) ```java // Implement Recipe<RecipeInput> and register RecipeSerializer + RecipeType public class MyRecipe implements Recipe<SingleRecipeInput> { // ... } ``` --- ## Tags Tags group blocks/items for use in recipes and game logic. `data/mymod/tags/block/mineable/pickaxe.json`: ```json { "replace": false, "values": ["mymod:my_block"] } ``` `data/mymod/tags/block/needs_iron_tool.json`: ```json { "replace": false, "values": ["mymod:my_block"] } ``` `data/mymod/tags/item/my_material.json`: ```json { "replace": false, "values": ["mymod:my_ingot", "mymod:my_nugget"] } ``` --- ## Data Generation (NeoForge) ### BlockState Provider ```java public class ModBlockStateProvider extends BlockStateProvider { public ModBlockStateProvider(PackOutput output, ExistingFileHelper helper) { super(output, MyMod.MOD_ID, helper); } @Override protected void registerStatesAndModels() { simpleBlock(ModBlocks.MY_BLOCK.get()); simpleBlock(ModBlocks.SPECIAL_BLOCK.get(), models().cubeAll("special_block", modLoc("block/special_block"))); // Slab: slabBlock((SlabBlock) ModBlocks.MY_SLAB.get(), modLoc("block/my_block"), modLoc("block/my_block")); // Stairs: stairsBlock((StairBlock) ModBlocks.MY_STAIRS.get(), modLoc("block/my_block")); } } ``` ### Item Model Provider ```java public class ModItemModelProvider extends ItemModelProvider { public ModItemModelProvider(PackOutput output, ExistingFileHelper helper) { super(output, MyMod.MOD_ID, helper); } @Override protected void registerModels() { // BlockItem models derived from block models: withExistingParent(ModItems.MY_BLOCK_ITEM.getId().getPath(), modLoc("block/my_block")); // Flat item (generated): basicItem(ModItems.MY_ITEM.get()); } } ``` ### Recipe Provider ```java public class ModRecipeProvider extends RecipeProvider { public ModRecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) { super(output, lookupProvider); } @Override protected void buildRecipes(RecipeOutput output) { ShapedRecipeBuilder.shaped(RecipeCategory.BUILDING_BLOCKS, ModItems.MY_BLOCK_ITEM.get(), 4) .pattern("SS") .pattern("SS") .define('S', Items.STONE) .unlockedBy("has_stone", has(Items.STONE)) .save(output); SimpleCookingRecipeBuilder.smelting( Ingredient.of(Tags.Items.ORES_IRON), RecipeCategory.MISC, ModItems.MY_INGOT.get(), 0.7f, 200) .unlockedBy("has_ore", has(Tags.Items.ORES_IRON)) .save(output, ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, "my_ingot_smelting")); } } ``` ### Loot Table Provider ```java public class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) { super(output, Set.of(), List.of( new SubProviderEntry(ModBlockLootTables::new, LootContextParamSets.BLOCK) ), lookupProvider); } public static class ModBlockLootTables extends BlockLootSubProvider { protected ModBlockLootTables(HolderLookup.Provider registries) { super(Set.of(), FeatureFlags.REGISTRY.allFlags(), registries); } @Override protected void generate() { dropSelf(ModBlocks.MY_BLOCK.get()); // Drop ore with fortune/silk-touch handling: add(ModBlocks.MY_ORE.get(), createOreDrop(ModBlocks.MY_ORE.get(), ModItems.MY_GEM.get())); } @Override protected Iterable<Block> getKnownBlocks() { return ModBlocks.BLOCKS.getEntries().stream() .map(DeferredHolder::get)::iterator; } } } ``` --- ## Language File (en_us.json) ```json { "block.mymod.my_block": "My Block", "item.mymod.my_item": "My Item", "item.mymod.my_food": "My Food", "entity.mymod.my_entity": "My Entity", "container.mymod.my_container": "My Container", "itemGroup.mymod.main_tab": "My Mod", "commands.mymod.give.success": "Gave %s x%s My Item" } ``` --- ## GitHub Actions CI Workflow ```yaml # .github/workflows/build.yml name: Build on: push: branches: [main, develop] pull_request: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: java-version: '21' distribution: 'microsoft' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Build with Gradle run: ./gradlew build - name: Run Game Tests run: ./gradlew runGameTestServer # For Fabric: ./gradlew runGametest - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: name: mod-jar path: build/libs/*.jar if-no-files-found: error ``` --- ## Modrinth / CurseForge Publishing ```groovy // build.gradle — Modrinth via Minotaur plugin modrinth { token = System.getenv("MODRINTH_TOKEN") projectId = "your-project-id" versionNumber = project.mod_version versionType = "release" uploadFile = jar gameVersions = ["1.21.11"] loaders = ["neoforge"] changelog = rootProject.file("CHANGELOG.md").text syncBodyFrom = rootProject.file("README.md").text } ``` ```groovy // Alternatively, use the official CurseForge Gradle plugin curseforge { apiKey = System.getenv("CURSEFORGE_TOKEN") project { id = "000000" changelogType = "markdown" changelog = file("CHANGELOG.md") releaseType = "release" addGameVersion "1.21.11" addGameVersion "NeoForge" mainArtifact jar } } ``` -
fabric-api.md 11.3 KB
# Fabric API Patterns (1.21.x) Reference for Fabric-specific code patterns. Fabric is a lightweight, stable modding platform focused on clean hooks and fast updates. Targets Minecraft 1.21.x with Java 21. --- ## Mod Entry Point ```java // MyMod.java — registered as "main" entrypoint in fabric.mod.json public class MyMod implements ModInitializer { public static final String MOD_ID = "mymod"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override public void onInitialize() { // Runs on both client and server after mod loading ModBlocks.register(); ModItems.register(); ModBlockEntities.register(); } } // MyModClient.java — registered as "client" entrypoint @Environment(EnvType.CLIENT) public class MyModClient implements ClientModInitializer { @Override public void onInitializeClient() { // Client-only setup: renderers, screens, keybinds BlockEntityRendererFactories.register(ModBlockEntities.MY_BLOCK_ENTITY, MyBlockEntityRenderer::new); HandledScreens.register(ModMenuTypes.MY_MENU, MyScreen::new); } } ``` --- ## fabric.mod.json ```json { "schemaVersion": 1, "id": "mymod", "version": "${version}", "name": "My Mod", "description": "A cool Minecraft mod.", "authors": ["YourName"], "contact": { "sources": "https://github.com/yourname/mymod", "issues": "https://github.com/yourname/mymod/issues" }, "license": "MIT", "icon": "assets/mymod/icon.png", "environment": "*", "entrypoints": { "main": ["com.example.mymod.MyMod"], "client": ["com.example.mymod.client.MyModClient"] }, "mixins": ["mymod.mixins.json"], "depends": { "fabricloader": ">=0.19.3", "fabric-api": ">=0.141.4+1.21.11", "minecraft": "~1.21.11" } } ``` --- ## Block & Item Registration ```java // ModBlocks.java public class ModBlocks { public static final Identifier MY_BLOCK_ID = Identifier.of(MyMod.MOD_ID, "my_block"); public static final RegistryKey<Block> MY_BLOCK_KEY = RegistryKey.of(RegistryKeys.BLOCK, MY_BLOCK_ID); public static final Block MY_BLOCK = new Block( AbstractBlock.Settings.create() .registryKey(MY_BLOCK_KEY) .mapColor(MapColor.STONE) .strength(1.5f, 6.0f) .sounds(BlockSoundGroup.STONE) .requiresTool() ); public static void register() { Registry.register(Registries.BLOCK, MY_BLOCK_KEY, MY_BLOCK); } } // ModItems.java public class ModItems { public static final Identifier MY_ITEM_ID = Identifier.of(MyMod.MOD_ID, "my_item"); public static final RegistryKey<Item> MY_ITEM_KEY = RegistryKey.of(RegistryKeys.ITEM, MY_ITEM_ID); public static final RegistryKey<Item> MY_BLOCK_ITEM_KEY = RegistryKey.of(RegistryKeys.ITEM, ModBlocks.MY_BLOCK_ID); public static final Item MY_ITEM = new Item( new Item.Settings().registryKey(MY_ITEM_KEY).maxCount(16) ); // BlockItem for a block public static final Item MY_BLOCK_ITEM = new BlockItem( ModBlocks.MY_BLOCK, new Item.Settings().registryKey(MY_BLOCK_ITEM_KEY) ); public static void register() { Registry.register(Registries.ITEM, MY_ITEM_KEY, MY_ITEM); Registry.register(Registries.ITEM, MY_BLOCK_ITEM_KEY, MY_BLOCK_ITEM); // Add to creative tab ItemGroupEvents.modifyEntriesEvent(ItemGroups.BUILDING_BLOCKS) .register(content -> content.add(MY_BLOCK_ITEM)); } } ``` --- ## Block Entity ```java // MyBlockEntity.java public class MyBlockEntity extends BlockEntity { private int processingTicks; public MyBlockEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.MY_BLOCK_ENTITY, pos, state); } @Override protected void writeData(WriteView view) { super.writeData(view); view.putInt("processing_ticks", processingTicks); } @Override protected void readData(ReadView view) { super.readData(view); processingTicks = view.getInt("processing_ticks", 0); } } // ModBlockEntities.java public class ModBlockEntities { public static final BlockEntityType<MyBlockEntity> MY_BLOCK_ENTITY = BlockEntityType.Builder.create(MyBlockEntity::new, ModBlocks.MY_BLOCK).build(); public static void register() { Registry.register(Registries.BLOCK_ENTITY_TYPE, Identifier.of(MyMod.MOD_ID, "my_block_entity"), MY_BLOCK_ENTITY); } } ``` In Yarn 1.21.11, block-entity persistence uses `ReadView` and `WriteView`. For compound state such as inventories, use the view's codec/list helpers; do not copy the earlier `readNbt` / `writeNbt` overloads into this version. --- ## Mixins Mixins patch Minecraft classes without modifying them. Use sparingly — prefer Fabric API hooks. ```java // mixin/MixinServerPlayer.java @Mixin(ServerPlayerEntity.class) public abstract class MixinServerPlayer { // @Inject — add code at a specific point @Inject(method = "tick", at = @At("HEAD")) private void onTickHead(CallbackInfo ci) { ServerPlayerEntity self = (ServerPlayerEntity)(Object) this; // runs at the beginning of ServerPlayerEntity.tick() } // @Overwrite — replace a method entirely (avoid if possible; breaks compat) // @ModifyVariable — modify a local variable // @Redirect — redirect a method call within a method // @Shadow — access a field or method from the target class @Shadow public abstract ServerWorld getServerWorld(); } ``` Mixin config file (`mymod.mixins.json`): ```json { "required": true, "minVersion": "0.8", "package": "com.example.mymod.mixin", "compatibilityLevel": "JAVA_21", "mixins": ["MixinServerPlayer"], "client": [], "server": [], "injectors": { "defaultRequire": 1 } } ``` --- ## Fabric Events Fabric API provides stable event hooks. Prefer these over Mixins. ```java // Register event callbacks in onInitialize() ServerTickEvents.END_SERVER_TICK.register(server -> { // runs at end of every server tick }); ServerLifecycleEvents.SERVER_STARTED.register(server -> { MyMod.LOGGER.info("Server started!"); }); UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { // return ActionResult.PASS to not consume the event return ActionResult.PASS; }); AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { return ActionResult.PASS; }); // Item use callback UseItemCallback.EVENT.register((player, world, hand) -> { ItemStack stack = player.getStackInHand(hand); return TypedActionResult.pass(stack); }); ``` --- ## Networking (Fabric 1.21 — ServerPlayNetworking) ```java // Define a payload record public record MyPayload(int data) implements CustomPayload { public static final CustomPayload.Id<MyPayload> ID = new CustomPayload.Id<>(Identifier.of(MyMod.MOD_ID, "my_payload")); public static final PacketCodec<PacketByteBuf, MyPayload> CODEC = PacketCodec.tuple(PacketCodecs.INTEGER, MyPayload::data, MyPayload::new); @Override public CustomPayload.Id<? extends CustomPayload> getId() { return ID; } } // Server-side: register receiver ServerPlayNetworking.registerGlobalReceiver(MyPayload.ID, (payload, context) -> { context.server().execute(() -> { ServerPlayerEntity player = context.player(); // handle payload }); }); // Send from server to client ServerPlayNetworking.send(serverPlayerEntity, new MyPayload(42)); // Client-side: register receiver ClientPlayNetworking.registerGlobalReceiver(MyPayload.ID, (payload, context) -> { context.client().execute(() -> { // handle on client }); }); // Send from client to server ClientPlayNetworking.send(new MyPayload(42)); ``` --- ## Commands (Fabric) ```java // Register commands via CommandRegistrationCallback CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { dispatcher.register( CommandManager.literal("mycommand") .requires(source -> source.hasPermissionLevel(2)) .then(CommandManager.argument("target", EntityArgumentType.player()) .executes(ctx -> { ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "target"); ctx.getSource().sendFeedback( () -> Text.literal("Hello, " + player.getName().getString()), false); return 1; })) ); }); ``` --- ## Custom Screen / GUI ```java // MyScreenHandler.java (server + client logic) public class MyScreenHandler extends ScreenHandler { public MyScreenHandler(int syncId, PlayerInventory playerInventory) { super(ModMenuTypes.MY_MENU, syncId); addPlayerInventory(playerInventory); addPlayerHotbar(playerInventory); } @Override public boolean canUse(PlayerEntity player) { return true; } private void addPlayerInventory(PlayerInventory playerInventory) { for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++) addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, 84 + row * 18)); } private void addPlayerHotbar(PlayerInventory playerInventory) { for (int col = 0; col < 9; col++) addSlot(new Slot(playerInventory, col, 8 + col * 18, 142)); } } // MyScreen.java (@Environment(EnvType.CLIENT)) @Environment(EnvType.CLIENT) public class MyScreen extends HandledScreen<MyScreenHandler> { private static final Identifier TEXTURE = Identifier.of(MyMod.MOD_ID, "textures/gui/my_gui.png"); public MyScreen(MyScreenHandler handler, PlayerInventory inventory, Text title) { super(handler, inventory, title); backgroundWidth = 176; backgroundHeight = 166; } @Override protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) { context.drawTexture(TEXTURE, x, y, 0, 0, backgroundWidth, backgroundHeight); } } ``` --- ## gradle.properties (Fabric Loom template) The examples in this reference use Yarn/Fabric-named classes such as `Identifier`, `ServerPlayerEntity`, and `AbstractBlock.Settings`. Use Yarn mappings for Fabric-only projects. Use official Mojang mappings only when a shared project intentionally standardizes on Mojmap and the snippets have been translated. ```properties org.gradle.jvmargs=-Xmx2G minecraft_version=1.21.11 loader_version=0.19.3 fabric_version=0.141.4+1.21.11 yarn_mappings=1.21.11+build.6 # Fabric-only examples in this file expect: # mappings("net.fabricmc:yarn:$yarn_mappings:v2") # Shared multiloader examples can use loom.officialMojangMappings() instead. mod_version=1.0.0 maven_group=com.example archives_base_name=mymod ``` --- ## Useful Fabric Classes (1.21.x Quick Reference) |Need|Class| |---|---| |Block settings|`AbstractBlock.Settings`| |Item settings|`Item.Settings`| |Identifier|`Identifier.of("mymod", "thing")`| |Map colours|`MapColor.*`| |Block sounds|`BlockSoundGroup.*`| |Tool materials|`ToolMaterials.*`| |Text/chat|`Text.literal("...")`, `Text.translatable("key")`| |Block entity persistence|`ReadView`, `WriteView`| |NBT values when an API explicitly requires them|`NbtCompound`, `NbtList`| |Registries|`Registries.*`| |Data pack registry|`RegistryKey.of(RegistryKeys.BIOME, id)`| |Fabric events|`ServerTickEvents`, `UseBlockCallback`, etc.| |Item group events|`ItemGroupEvents.modifyEntriesEvent(ItemGroups.*)`| -
forge-1.20.1-api.md 13 KB
# Forge 1.20.1 API Patterns Use this reference only for Minecraft `1.20.1` projects using MinecraftForge `47.4.x` and Java 17. Do not apply these snippets to NeoForge 1.21.x projects. Forge 1.20.1 and NeoForge 1.21.x share some concepts, but the package names, metadata file, event bus access, helper classes, and several data paths differ. ## Project Detection A Forge 1.20.1 project usually has these signatures: ```text build.gradle: id 'net.minecraftforge.gradle' version '[6.0,6.2)' gradle.properties: minecraft_version=1.20.1 gradle.properties: forge_version=47.4.20 src/main/resources/META-INF/mods.toml ``` Use Java 17 for Forge 1.20.1. The official MDK template sets: ```gradle java.toolchain.languageVersion = JavaLanguageVersion.of(17) ``` ## Gradle Properties ```properties org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false minecraft_version=1.20.1 minecraft_version_range=[1.20.1,1.21) forge_version=47.4.20 forge_version_range=[47,) loader_version_range=[47,) mapping_channel=official mapping_version=1.20.1 mod_id=mymod mod_name=My Mod mod_license=MIT mod_version=1.0.0 mod_group_id=com.example.mymod mod_authors=YourName mod_description=A Forge 1.20.1 mod. ``` Use the current Forge 1.20.1 `47.4.x` patch from the Forge files page when scaffolding a new project. Keep `minecraft_version` and the left side of the Forge artifact coordinate paired as `1.20.1-${forge_version}`. ## build.gradle Essentials ```gradle plugins { id 'eclipse' id 'idea' id 'maven-publish' id 'net.minecraftforge.gradle' version '[6.0,6.2)' } version = mod_version group = mod_group_id base { archivesName = mod_id } java.toolchain.languageVersion = JavaLanguageVersion.of(17) minecraft { mappings channel: mapping_channel, version: mapping_version copyIdeResources = true runs { configureEach { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' mods { "${mod_id}" { source sourceSets.main } } } client { property 'forge.enabledGameTestNamespaces', mod_id } server { property 'forge.enabledGameTestNamespaces', mod_id args '--nogui' } data { workingDirectory project.file('run-data') args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') } } } sourceSets.main.resources { srcDir 'src/generated/resources' } dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" } ``` ## mods.toml Forge 1.20.1 uses `src/main/resources/META-INF/mods.toml`, not `neoforge.mods.toml`. ```toml modLoader="javafml" loaderVersion="${loader_version_range}" license="${mod_license}" [[mods]] modId="${mod_id}" version="${mod_version}" displayName="${mod_name}" authors="${mod_authors}" description='''${mod_description}''' [[dependencies.${mod_id}]] modId="forge" mandatory=true versionRange="${forge_version_range}" ordering="NONE" side="BOTH" [[dependencies.${mod_id}]] modId="minecraft" mandatory=true versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" ``` ## Entry Point And Event Buses Forge 1.20.1 uses `net.minecraftforge.*` imports. The mod event bus comes from `FMLJavaModLoadingContext`, and gameplay events use `MinecraftForge.EVENT_BUS`. ```java @Mod(MyMod.MOD_ID) public class MyMod { public static final String MOD_ID = "mymod"; private static final Logger LOGGER = LogUtils.getLogger(); public MyMod(FMLJavaModLoadingContext context) { IEventBus modEventBus = context.getModEventBus(); ModBlocks.BLOCKS.register(modEventBus); ModItems.ITEMS.register(modEventBus); ModCreativeTabs.CREATIVE_MODE_TABS.register(modEventBus); modEventBus.addListener(this::commonSetup); modEventBus.addListener(this::addCreative); MinecraftForge.EVENT_BUS.register(this); } private void commonSetup(FMLCommonSetupEvent event) { LOGGER.info("Common setup for {}", MOD_ID); } private void addCreative(BuildCreativeModeTabContentsEvent event) { if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) { event.accept(ModItems.MY_BLOCK_ITEM); } } @SubscribeEvent public void onServerStarting(ServerStartingEvent event) { LOGGER.info("Server starting"); } } ``` ## DeferredRegister Forge 1.20.1 uses `ForgeRegistries` and `RegistryObject`. ```java public final class ModBlocks { public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MyMod.MOD_ID); public static final RegistryObject<Block> MY_BLOCK = BLOCKS.register( "my_block", () -> new Block(BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .strength(1.5f, 6.0f) .sound(SoundType.STONE) .requiresCorrectToolForDrops()) ); private ModBlocks() { } } ``` ```java public final class ModItems { public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MyMod.MOD_ID); public static final RegistryObject<Item> MY_BLOCK_ITEM = ITEMS.register( "my_block", () -> new BlockItem(ModBlocks.MY_BLOCK.get(), new Item.Properties()) ); public static final RegistryObject<Item> MY_ITEM = ITEMS.register( "my_item", () -> new Item(new Item.Properties()) ); private ModItems() { } } ``` For vanilla registries not wrapped by `ForgeRegistries`, use the matching `net.minecraft.core.registries.Registries` key with `DeferredRegister.create`. Creative mode tabs in the official MDK use this pattern. ```java public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MyMod.MOD_ID); ``` ## ResourceLocation Forge 1.20.1 uses the public constructors available in Minecraft 1.20.1. Do not use NeoForge 1.21.x helpers such as `ResourceLocation.fromNamespaceAndPath`. ```java ResourceLocation id = new ResourceLocation(MyMod.MOD_ID, "my_block"); ``` ## Networking Forge 1.20.1 uses `SimpleChannel` with `NetworkRegistry.newSimpleChannel`. Register packets with stable integer discriminators and always mark packets as handled after scheduling work. ```java public final class ModNetworking { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(MyMod.MOD_ID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void register() { int id = 0; CHANNEL.registerMessage( id++, SyncEnergyPacket.class, SyncEnergyPacket::encode, SyncEnergyPacket::decode, SyncEnergyPacket::handle ); } private ModNetworking() { } } ``` ```java public record SyncEnergyPacket(BlockPos pos, int energy) { public static void encode(SyncEnergyPacket packet, FriendlyByteBuf buffer) { buffer.writeBlockPos(packet.pos()); buffer.writeInt(packet.energy()); } public static SyncEnergyPacket decode(FriendlyByteBuf buffer) { return new SyncEnergyPacket(buffer.readBlockPos(), buffer.readInt()); } public static void handle(SyncEnergyPacket packet, Supplier<NetworkEvent.Context> context) { NetworkEvent.Context ctx = context.get(); ctx.enqueueWork(() -> { ServerPlayer sender = ctx.getSender(); if (sender == null || !sender.level().hasChunkAt(packet.pos())) { return; } // Handle trusted server-side work here. }); ctx.setPacketHandled(true); } } ``` Send packets through the channel and Forge `PacketDistributor` helpers. In Forge `47.4.x`, `PacketDistributor<T>.with` takes the target object directly; for `PLAYER`, pass the `ServerPlayer`, not a `Supplier<ServerPlayer>`. ```java ModNetworking.CHANNEL.send(PacketDistributor.PLAYER.with(serverPlayer), packet); ModNetworking.CHANNEL.sendToServer(packet); ``` ## Data Generation And Paths Run Forge 1.20.1 data generation with `./gradlew runData`. The generated output is normally wired from `src/generated/resources` into `sourceSets.main.resources`. Register data providers on the mod event bus; Forge fires `GatherDataEvent` there when the data generator starts. ```java public MyMod(FMLJavaModLoadingContext context) { IEventBus modEventBus = context.getModEventBus(); modEventBus.addListener(ModDataGen::gatherData); } ``` Use Forge 1.20.1 provider classes and constructors rather than NeoForge 1.21.x examples. `ExistingFileHelper` comes from the event and validates referenced assets such as textures and parent models. ```java public final class ModDataGen { public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider(); generator.addProvider(event.includeClient(), new ModBlockStateProvider(output, existingFileHelper)); generator.addProvider(event.includeClient(), new ModItemModelProvider(output, existingFileHelper)); generator.addProvider(event.includeServer(), new ModRecipeProvider(output)); ModBlockTagsProvider blockTags = new ModBlockTagsProvider(output, lookupProvider, existingFileHelper); generator.addProvider(event.includeServer(), blockTags); generator.addProvider(event.includeServer(), new ModItemTagsProvider( output, lookupProvider, blockTags.contentsGetter(), existingFileHelper )); } private ModDataGen() { } } ``` Common Forge 1.20.1 provider constructors look like this: ```java public final class ModBlockStateProvider extends BlockStateProvider { public ModBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) { super(output, MyMod.MOD_ID, existingFileHelper); } @Override protected void registerStatesAndModels() { // Generate blockstates, block models, and simple block item models here. } } ``` ```java public final class ModItemModelProvider extends ItemModelProvider { public ModItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) { super(output, MyMod.MOD_ID, existingFileHelper); } @Override protected void registerModels() { // Generate standalone item models here. } } ``` ```java public final class ModRecipeProvider extends RecipeProvider { public ModRecipeProvider(PackOutput output) { super(output); } @Override protected void buildRecipes(Consumer<FinishedRecipe> consumer) { // Generate recipes here. } } ``` ```java public final class ModBlockTagsProvider extends BlockTagsProvider { public ModBlockTagsProvider( PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, ExistingFileHelper existingFileHelper ) { super(output, lookupProvider, MyMod.MOD_ID, existingFileHelper); } @Override protected void addTags(HolderLookup.Provider provider) { // Generate block tags here. } } ``` ```java public final class ModItemTagsProvider extends ItemTagsProvider { public ModItemTagsProvider( PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, CompletableFuture<TagsProvider.TagLookup<Block>> blockTags, ExistingFileHelper existingFileHelper ) { super(output, lookupProvider, blockTags, MyMod.MOD_ID, existingFileHelper); } @Override protected void addTags(HolderLookup.Provider provider) { // Generate item tags here. } } ``` Minecraft 1.20.1 uses the older server-data path names: ```text data/<modid>/loot_tables/blocks/<block>.json data/<modid>/tags/blocks/<tag>.json data/<modid>/tags/items/<tag>.json ``` Do not rewrite these to the 1.21.x singular path names when editing a Forge 1.20.1 project. Conversely, do not copy these legacy paths into 1.21.x projects. ## Common Porting Mistakes | Mistake | Forge 1.20.1 fix | |---|---| | Using `net.neoforged.*` imports | Use `net.minecraftforge.*` imports | | Creating `META-INF/neoforge.mods.toml` | Use `META-INF/mods.toml` | | Injecting `IEventBus` into the mod constructor | Accept `FMLJavaModLoadingContext context` and call `context.getModEventBus()` | | Using `DeferredBlock` / `DeferredItem` | Use `RegistryObject<T>` | | Using `BuiltInRegistries.BLOCK` with Forge deferred registers | Use `ForgeRegistries.BLOCKS` | | Using `ResourceLocation.fromNamespaceAndPath` | Use `new ResourceLocation(namespace, path)` | | Assuming Java 21 | Use Java 17 for Minecraft 1.20.1 | -
neoforge-api.md 12.4 KB
# NeoForge API Patterns (1.21.x) Legacy NeoForge-specific code patterns for Minecraft 1.21.x with Java 21. Use the exact project's versioned documentation for current 26.x / Java 25 work. --- ## Mod Entry Point ```java // MyMod.java @Mod(MyMod.MOD_ID) public class MyMod { public static final String MOD_ID = "mymod"; public static final Logger LOGGER = LogUtils.getLogger(); public MyMod(IEventBus modEventBus) { // Register deferred registers with the mod event bus ModBlocks.BLOCKS.register(modEventBus); ModItems.ITEMS.register(modEventBus); ModBlockEntities.BLOCK_ENTITIES.register(modEventBus); ModMenuTypes.MENUS.register(modEventBus); ModEntityTypes.ENTITY_TYPES.register(modEventBus); ModSounds.SOUNDS.register(modEventBus); // Register mod event listeners modEventBus.addListener(this::commonSetup); modEventBus.addListener(this::addCreativeTabItems); // Register for in-game events on the GAME event bus (NeoForge 1.20.5+) NeoForge.EVENT_BUS.register(this); // Prefer @EventBusSubscriber on a separate class over registering `this` } private void commonSetup(FMLCommonSetupEvent event) { event.enqueueWork(() -> { // thread-safe registration calls go here, e.g. CompostingChanceRegistry }); } private void addCreativeTabItems(BuildCreativeModeTabContentsEvent event) { if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) { event.accept(ModItems.MY_ITEM); } } } ``` --- ## neoforge.mods.toml (META-INF/neoforge.mods.toml) > File renamed from `mods.toml` to `neoforge.mods.toml` in NeoForge 1.20.5+. > Always use `neoforge.mods.toml` for 1.21.x projects. ```toml modLoader="javafml" loaderVersion="[1,)" license="MIT" [[mods]] modId="mymod" version="${file.jarVersion}" displayName="My Mod" description=''' A brief description of what my mod does. ''' logoFile="mymod.png" [[dependencies.mymod]] modId="neoforge" type="required" versionRange="[21.11,)" ordering="NONE" side="BOTH" [[dependencies.mymod]] modId="minecraft" type="required" versionRange="[1.21.11,1.22)" ordering="NONE" side="BOTH" ``` --- ## DeferredRegister Patterns ```java // ModBlocks.java public class ModBlocks { public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MyMod.MOD_ID); // Simple full-cube block public static final DeferredBlock<Block> MY_BLOCK = BLOCKS.registerSimpleBlock("my_block", BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .instrument(NoteBlockInstrument.BASEDRUM) .strength(1.5f, 6.0f) .sound(SoundType.STONE) .requiresCorrectToolForDrops()); // Custom block class public static final DeferredBlock<MySpecialBlock> SPECIAL_BLOCK = BLOCKS.registerBlock("special_block", MySpecialBlock::new, BlockBehaviour.Properties.of().strength(2.0f)); } ``` ```java // ModItems.java public class ModItems { public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MyMod.MOD_ID); // BlockItem for a block public static final DeferredItem<BlockItem> MY_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.MY_BLOCK); // Simple item public static final DeferredItem<Item> MY_ITEM = ITEMS.registerSimpleItem("my_item", new Item.Properties().stacksTo(16)); // Custom item class public static final DeferredItem<MyCustomItem> MY_CUSTOM_ITEM = ITEMS.registerItem("my_custom_item", MyCustomItem::new); } ``` For 26.x tool and armor APIs, use the explicitly versioned patterns in `common-patterns.md`; this reference otherwise retains 1.21.x examples. --- ## Block Entity ```java // MyBlockEntity.java public class MyBlockEntity extends BlockEntity { private int processingTicks; public MyBlockEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.MY_BLOCK_ENTITY.get(), pos, state); } @Override protected void saveAdditional(ValueOutput output) { super.saveAdditional(output); output.putInt("processing_ticks", processingTicks); } @Override public void loadAdditional(ValueInput input) { super.loadAdditional(input); processingTicks = input.getIntOr("processing_ticks", 0); } } // ModBlockEntities.java public class ModBlockEntities { public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, MyMod.MOD_ID); public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<MyBlockEntity>> MY_BLOCK_ENTITY = BLOCK_ENTITIES.register("my_block_entity", () -> BlockEntityType.Builder .of(MyBlockEntity::new, ModBlocks.MY_BLOCK.get()) .build(null)); } ``` --- ## Event Bus System NeoForge has two event buses: - **MOD bus** (`IEventBus` injected into constructor): lifecycle events, registration events - **GAME bus** (`NeoForge.EVENT_BUS`): in-game events Import: `net.neoforged.neoforge.common.NeoForge` (`MinecraftForge.EVENT_BUS` was removed in NeoForge 1.20.5) ```java // Recommended: separate class with @EventBusSubscriber @EventBusSubscriber(modid = MyMod.MOD_ID, bus = Bus.MOD) public class ModEvents { @SubscribeEvent public static void onRegisterEntityRenderers(EntityRenderersEvent.RegisterRenderers event) { event.registerEntityRenderer(ModEntityTypes.MY_ENTITY.get(), MyEntityRenderer::new); } @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator gen = event.getGenerator(); PackOutput output = gen.getPackOutput(); ExistingFileHelper helper = event.getExistingFileHelper(); CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider(); gen.addProvider(event.includeClient(), new ModBlockStateProvider(output, helper)); gen.addProvider(event.includeClient(), new ModItemModelProvider(output, helper)); gen.addProvider(event.includeServer(), new ModRecipeProvider(output, lookupProvider)); gen.addProvider(event.includeServer(), new ModLootTableProvider(output, lookupProvider)); gen.addProvider(event.includeServer(), new ModBlockTagsProvider(output, lookupProvider, helper)); } } @EventBusSubscriber(modid = MyMod.MOD_ID, bus = Bus.GAME) public class GameEvents { @SubscribeEvent public static void onPlayerTick(PlayerTickEvent.Post event) { // runs every tick for every player } @SubscribeEvent public static void onLivingHurt(LivingIncomingDamageEvent event) { // fires when an entity is about to take damage; cancellable } } ``` --- ## Menu / GUI (Container) ```java // MyMenu.java (server + client) public class MyMenu extends AbstractContainerMenu { private final ContainerLevelAccess access; public MyMenu(int containerId, Inventory playerInventory) { this(containerId, playerInventory, ContainerLevelAccess.NULL); } public MyMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) { super(ModMenuTypes.MY_MENU.get(), containerId); this.access = access; addPlayerInventory(playerInventory); addPlayerHotbar(playerInventory); } @Override public boolean stillValid(Player player) { return access.evaluate( (level, pos) -> player.distanceToSqr(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) < 64, true); } } // MyScreen.java (@OnlyIn(Dist.CLIENT)) @OnlyIn(Dist.CLIENT) public class MyScreen extends AbstractContainerScreen<MyMenu> { private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, "textures/gui/my_gui.png"); public MyScreen(MyMenu menu, Inventory playerInventory, Component title) { super(menu, playerInventory, title); this.imageWidth = 176; this.imageHeight = 166; } @Override protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) { graphics.blit(TEXTURE, leftPos, topPos, 0, 0, imageWidth, imageHeight); } } // Register on MOD bus (client-only): @EventBusSubscriber(modid = MyMod.MOD_ID, bus = Bus.MOD, value = Dist.CLIENT) public class ClientModEvents { @SubscribeEvent public static void registerScreens(RegisterMenuScreensEvent event) { event.register(ModMenuTypes.MY_MENU.get(), MyScreen::new); } } ``` --- ## Capabilities (NeoForge 1.21.11) NeoForge registers providers for concrete block entities, blocks, entity types, or items on the mod event bus. Its capability lookup returns the implementation or `null`. Do not use Forge's `AttachCapabilitiesEvent`, `ICapabilityProvider`, or `LazyOptional` patterns in a NeoForge 1.21.11 project. ```java @EventBusSubscriber(modid = MyMod.MOD_ID, bus = Bus.MOD) public final class ModCapabilities { @SubscribeEvent public static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlockEntity( Capabilities.Item.BLOCK, ModBlockEntities.MY_BLOCK_ENTITY.get(), (blockEntity, side) -> blockEntity.getItemHandler(side) ); } } // Query from a level. A null result means this side has no item handler. ResourceHandler<ItemResource> handler = level.getCapability(Capabilities.Item.BLOCK, pos, Direction.NORTH); if (handler != null) { // Use handler. } ``` For a custom capability, create a static `BlockCapability`, `EntityCapability`, or `ItemCapability` and register its provider with the same event. See the [NeoForge 1.21.11 capability guide](https://docs.neoforged.net/docs/1.21.11/inventories/capabilities/). --- ## Network Packets (1.21 SimpleChannel) ```java // Register a payload (packet) type public record MyPayload(int value) implements CustomPacketPayload { public static final Type<MyPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, "my_payload")); public static final StreamCodec<ByteBuf, MyPayload> STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.INT, MyPayload::value, MyPayload::new); @Override public Type<? extends CustomPacketPayload> type() { return TYPE; } } // Register on MOD bus @SubscribeEvent public static void registerPayloads(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); registrar.playToClient(MyPayload.TYPE, MyPayload.STREAM_CODEC, (payload, ctx) -> { // handle on client — ctx.enqueueWork() for thread safety ctx.enqueueWork(() -> handleOnClient(payload)); }); registrar.playToServer(MyPayload.TYPE, MyPayload.STREAM_CODEC, (payload, ctx) -> ctx.enqueueWork(() -> handleOnServer(payload, ctx.player()))); } // Send from server to client PacketDistributor.sendToPlayer(serverPlayer, new MyPayload(42)); // Send from client to server PacketDistributor.sendToServer(new MyPayload(42)); ``` --- ## Biome Modifier (World Gen Integration) `data/mymod/neoforge/biome_modifier/add_spawn.json`: ```json { "type": "neoforge:add_spawns", "biomes": "#minecraft:is_overworld", "spawners": [ { "type": "mymod:my_entity", "weight": 10, "minCount": 2, "maxCount": 4 } ] } ``` --- ## gradle.properties (NeoForge MDK template) ```properties org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false minecraft_version=1.21.11 minecraft_version_range=[1.21.11,1.22) neo_version=21.11.42 neo_version_range=[21.11,) loader_version_range=[1,) mod_id=mymod mod_name=My Mod mod_license=MIT mod_version=1.0.0 mod_group_id=com.example.mymod mod_authors=YourName mod_description=A cool Minecraft mod. ## Dependencies (optional) # patchouli_version=... ``` --- ## Useful NeoForge Classes (1.21.x Quick Reference) |Need|Class| |---|---| |Block properties|`BlockBehaviour.Properties`| |Item properties|`Item.Properties`| |Map colours|`MapColor.*`| |Block sounds|`SoundType.*`| |Tool tiers|`Tiers.*` (WOOD, STONE, IRON, DIAMOND, NETHERITE)| |Rarity|`Rarity.*` (COMMON, UNCOMMON, RARE, EPIC)| |Block tags|`BlockTags.*`| |Item tags|`ItemTags.*`| |Entity categories|`MobCategory.*`| |Directions|`Direction.*`| |Block positions|`BlockPos`, `BlockPos.MutableBlockPos`| |Level access|`Level`, `ServerLevel`| |Registry access|`BuiltInRegistries.*`, `Registries.*`|
-
-
scripts
-
check-build.sh 5.8 KB
#!/usr/bin/env bash # check-build.sh # Verifies the Minecraft mod build environment and runs the project's build task. # Run from the root of a Minecraft mod project. set -euo pipefail PASS="[PASS]" FAIL="[FAIL]" WARN="[WARN]" check_one_of() { local label="$1" shift for f in "$@"; do if [[ -f "$f" ]]; then echo "$PASS $f" return 0 fi done echo "$WARN $label not found" } read_gradle_property() { local key="$1" if [[ -f "gradle.properties" ]]; then sed -n "s/^${key}=//p" gradle.properties | head -1 fi } parse_java_major() { local version="$1" if [[ "$version" =~ ^1\.([0-9]+) ]]; then echo "${BASH_REMATCH[1]}" return 0 fi if [[ "$version" =~ ^([0-9]+) ]]; then echo "${BASH_REMATCH[1]}" return 0 fi return 1 } version_at_least() { local found="$1" local required="$2" [[ "$found" =~ ^[0-9]+$ ]] || return 1 [[ "$found" -ge "$required" ]] } echo "=== Minecraft Mod Build Environment Check ===" echo "" BUILD_FILES=(build.gradle build.gradle.kts settings.gradle settings.gradle.kts gradle.properties) MINECRAFT_VERSION=$(read_gradle_property "minecraft_version") # Platform detection is needed before the Java check because Forge 1.20.1 targets Java 17. echo "Detecting mod platform..." PLATFORM="unknown" if grep -qr "architectury" "${BUILD_FILES[@]}" 2>/dev/null; then PLATFORM="architectury" elif grep -qr "net.neoforged" "${BUILD_FILES[@]}" 2>/dev/null; then PLATFORM="neoforge" elif grep -qr "net.minecraftforge" "${BUILD_FILES[@]}" 2>/dev/null; then PLATFORM="forge" elif grep -qr "fabric-loom\|fabricmc" "${BUILD_FILES[@]}" 2>/dev/null; then PLATFORM="fabric" fi if [[ -n "$MINECRAFT_VERSION" ]]; then echo "$PASS Platform: $PLATFORM (Minecraft $MINECRAFT_VERSION)" else echo "$PASS Platform: $PLATFORM" fi REQUIRED_JAVA=21 JAVA_REASON="Minecraft 1.20.5+ / 1.21.x requires Java 21" if [[ "$MINECRAFT_VERSION" =~ ^([0-9]+)\.([0-9]+) ]] && (( BASH_REMATCH[1] >= 26 )); then REQUIRED_JAVA=25 JAVA_REASON="Minecraft 26.x requires Java 25" elif [[ "$PLATFORM" == "forge" && "$MINECRAFT_VERSION" == "1.20.1" ]]; then REQUIRED_JAVA=17 JAVA_REASON="Forge 1.20.1 requires Java 17+ and should target Java 17" fi # Java version echo "" echo "Checking Java version..." if ! command -v java &>/dev/null; then echo "$FAIL java not found. Install JDK $REQUIRED_JAVA from https://adoptium.net/" exit 1 fi JAVA_VERSION=$(java -version 2>&1 | head -1 | sed -n 's/.*version "\([^"]*\)".*/\1/p') JAVA_MAJOR=$(parse_java_major "$JAVA_VERSION" || true) if [[ -z "$JAVA_MAJOR" ]]; then echo "$FAIL Could not parse Java version from: $JAVA_VERSION" exit 1 fi if version_at_least "$JAVA_MAJOR" "$REQUIRED_JAVA"; then echo "$PASS Java $JAVA_VERSION (JDK $REQUIRED_JAVA+ available)" if [[ "$PLATFORM" == "forge" && "$MINECRAFT_VERSION" == "1.20.1" && "$JAVA_MAJOR" -gt 17 ]]; then echo "$WARN Forge 1.20.1 projects should still compile with a Java 17 toolchain target" fi else echo "$FAIL Java $JAVA_VERSION - $JAVA_REASON" echo " Install from: https://adoptium.net/temurin/releases/?version=$REQUIRED_JAVA" exit 1 fi # Gradle wrapper echo "" echo "Checking Gradle wrapper..." if [[ ! -f "gradlew" ]]; then echo "$FAIL gradlew not found. Are you in the root of a Minecraft mod project?" exit 1 fi echo "$PASS gradlew found" # Key files echo "" echo "Checking key mod files..." case "$PLATFORM" in neoforge) for f in "src/main/resources/META-INF/neoforge.mods.toml" "gradle.properties"; do [[ -f "$f" ]] && echo "$PASS $f" || echo "$WARN $f not found" done [[ -f "build.gradle" || -f "build.gradle.kts" ]] && echo "$PASS build script found" || echo "$WARN build script not found" ;; forge) for f in "src/main/resources/META-INF/mods.toml" "gradle.properties"; do [[ -f "$f" ]] && echo "$PASS $f" || echo "$WARN $f not found" done if [[ "$MINECRAFT_VERSION" != "1.20.1" ]]; then echo "$WARN Forge support in this skill is documented for Minecraft 1.20.1; verify other Forge versions upstream" fi [[ -f "build.gradle" || -f "build.gradle.kts" ]] && echo "$PASS build script found" || echo "$WARN build script not found" ;; fabric) for f in "src/main/resources/fabric.mod.json" "gradle.properties"; do [[ -f "$f" ]] && echo "$PASS $f" || echo "$WARN $f not found" done [[ -f "build.gradle" || -f "build.gradle.kts" ]] && echo "$PASS build script found" || echo "$WARN build script not found" ;; architectury) check_one_of "common build script" "common/build.gradle" "common/build.gradle.kts" check_one_of "fabric build script" "fabric/build.gradle" "fabric/build.gradle.kts" check_one_of "neoforge build script" "neoforge/build.gradle" "neoforge/build.gradle.kts" [[ -f "gradle.properties" ]] && echo "$PASS gradle.properties" || echo "$WARN gradle.properties not found" ;; *) echo "$WARN Unknown platform; skipping file checks" ;; esac # Run Gradle build echo "" echo "Running ./gradlew build..." ./gradlew build --console=plain JAR_COUNT=$(find . -type f -path "*/build/libs/*.jar" ! -name "*-sources.jar" ! -name "*-dev.jar" 2>/dev/null | wc -l) if [[ "$JAR_COUNT" -gt 0 ]]; then echo "" echo "$PASS Gradle build completed. Candidate output jar(s):" find . -type f -path "*/build/libs/*.jar" ! -name "*-sources.jar" ! -name "*-dev.jar" | while read -r jar; do echo " -> $jar" done else echo "" echo "$FAIL Build did not produce a jar. Check Gradle output above." exit 1 fi echo "" echo "$WARN Candidate jars may predate this incremental build. Identify the intended distributable from Gradle output and run project-specific release validation before publishing." echo "=== Build environment check complete ==="
-
-
SKILL.md 16.9 KB
--- name: minecraft-modding description: "Create, modify, debug, or migrate Minecraft mods for current NeoForge or Fabric 26.x, legacy 1.21.x, and Forge 1.20.1. Use for loader-based gameplay code and assets; use minecraft-multiloader when one codebase must target both modern loaders." --- # Minecraft Modding Skill ## Overview Supported platforms: | Platform | MC Version | Java | Build System | |---|---|---|---| | **NeoForge** | 26.x current; 1.21.11 examples retained | Java 25 current; Java 21 on 1.21.x | Gradle + ModDevGradle | | **Forge** | 1.20.1 legacy lane | Java 17 | Gradle + ForgeGradle 6 | | **Fabric** | 26.x current; 1.21.11 examples retained | Java 25 current; Java 21 on 1.21.x | Gradle + Fabric Loom | | **Architectury** (multiloader) | 26.x or 1.21.x | Match Minecraft | Gradle + Architectury Loom | Always confirm the platform and Minecraft version from `gradle.properties` or `build.gradle` before writing any mod-specific code. Minecraft 26.1 introduced Java 25 and unobfuscated game executables. For 26.x projects, start from the current loader generator or example mod and preserve its build layout. Do not copy the 1.21.11 mapping, Loom plugin, remapping task, or Java 21 snippets in this skill into a 26.x project. Fabric 26.x uses the non-remapping Loom path and official names; NeoForge 26.x should start from the current NeoForge generator. The detailed API references cover legacy 1.21.x examples unless a section explicitly says 26.x. ### Routing Boundaries - `Use when`: the task is Java/Kotlin mod code, registry/event work, networking, datagen wiring, and loader APIs. - `Do not use when`: the task is command-only vanilla logic (`minecraft-commands-scripting`) or pure datapacks (`minecraft-datapack`). - `Do not use when`: the task targets Paper/Bukkit plugins (`minecraft-plugin-dev`). --- ## 1. Identifying the Platform ```bash # NeoForge project signature grep -r "net.neoforged" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5 # Forge 1.20.1 project signature grep -r "net.minecraftforge" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5 # Fabric project signature grep -r "fabric" gradle.properties build.gradle settings.gradle 2>/dev/null | head -5 # Read mod ID and version cat gradle.properties ``` Key files per platform: - **NeoForge**: `src/main/resources/META-INF/neoforge.mods.toml`, annotated `@Mod` main class - **Forge 1.20.1**: `src/main/resources/META-INF/mods.toml`, `net.minecraftforge:forge` dependency - **Fabric**: `src/main/resources/fabric.mod.json`, class implementing `ModInitializer` - **Architectury**: `common/`, `fabric/`, `neoforge/` subprojects --- ## 2. Build & Test Commands ```bash # Build the mod jar ./gradlew build # Run the Minecraft client to test ./gradlew runClient # Run a dedicated server to test ./gradlew runServer # Run game tests (NeoForge JUnit-style game tests) ./gradlew runGameTestServer # Run data generation (generates JSON assets automatically) ./gradlew runData # Remove this project's generated build outputs before a fresh rebuild ./gradlew clean # Check for dependency updates (optional) ./gradlew dependencyUpdates ``` `./gradlew build` runs the project's configured build tasks. Candidate mod jars are usually under `build/libs/`, but task names and file names are project-specific. Check the build result, then identify the intended distributable before publishing it. --- ## 3. Project Layout (NeoForge) ``` src/ main/ java/<groupId>/<modid>/ MyMod.java ← @Mod entry point block/ ModBlocks.java ← DeferredRegister.Blocks MyCustomBlock.java item/ ModItems.java ← DeferredRegister.Items entity/ ModEntities.java ← DeferredRegister.Entities menu/ ← custom GUI containers recipe/ worldgen/ datagen/ ModDataGen.java ← GatherDataEvent handler providers/ resources/ META-INF/ neoforge.mods.toml ← mod metadata (renamed from mods.toml in NeoForge 1.20.5+) assets/<modid>/ blockstates/ ← JSON blockstate definitions models/ block/ ← block model JSON item/ ← item model JSON items/ ← 1.21.x item-definition JSON textures/ block/ ← 16×16 PNG textures item/ lang/ en_us.json ← translation strings data/<modid>/ recipe/ ← crafting recipe JSON (26.x) loot_table/ blocks/ ← per-block loot table JSON tags/ blocks/ items/ ``` ## 4. Project Layout (Forge 1.20.1) Use this layout only when `minecraft_version=1.20.1` and the project depends on `net.minecraftforge:forge`. Forge 1.20.1 is not NeoForge: keep `mods.toml`, `net.minecraftforge.*` imports, Java 17, and ForgeGradle 6 patterns. ``` src/ main/ java/<groupId>/<modid>/ MyMod.java <- @Mod entry point block/ ModBlocks.java <- DeferredRegister.Blocks item/ ModItems.java <- DeferredRegister.Items datagen/ ModDataGen.java <- GatherDataEvent handler resources/ META-INF/ mods.toml <- Forge metadata assets/<modid>/ <- client assets data/<modid>/ <- server data using 1.20.1 paths ``` See `references/forge-1.20.1-api.md` before editing Forge 1.20.1 projects. ## 5. Project Layout (Fabric) ``` src/ main/ java/<groupId>/<modid>/ MyMod.java ← implements ModInitializer client/ MyModClient.java ← implements ClientModInitializer block/ item/ mixin/ ← Mixin classes resources/ fabric.mod.json assets/<modid>/ ← same as NeoForge data/<modid>/ ← same as NeoForge <modid>.mixins.json ← mixin configuration ``` --- ## 6. Core Concepts Cheatsheet ### Sides - **Physical client** – the game client JAR (has rendering code) - **Physical server** – the dedicated server JAR (no rendering) - **Logical client** – the client thread (handles rendering, input) - **Logical server** – the server thread (handles world simulation) - Code decorated with `@OnlyIn(Dist.CLIENT)` (NeoForge) or `@Environment(EnvType.CLIENT)` (Fabric) must NEVER run on the server. ### Registries Everything in Minecraft lives in a registry. Always register objects; never construct them at field initializer time outside a registry call. Use the mapping-appropriate registry constants for the loader you are editing: | Type | NeoForge / Mojang mappings | Fabric / Yarn mappings | |------|-----------------------------|-------------------------| | Blocks | `BuiltInRegistries.BLOCK` | `Registries.BLOCK` | | Items | `BuiltInRegistries.ITEM` | `Registries.ITEM` | | Entity types | `BuiltInRegistries.ENTITY_TYPE` | `Registries.ENTITY_TYPE` | | Block entity types | `BuiltInRegistries.BLOCK_ENTITY_TYPE` | `Registries.BLOCK_ENTITY_TYPE` | | Menu / screen-handler types | `BuiltInRegistries.MENU` | `Registries.SCREEN_HANDLER` | | Sound events | `BuiltInRegistries.SOUND_EVENT` | `Registries.SOUND_EVENT` | | Biomes | `Registries.BIOME` registry keys | `RegistryKeys.BIOME` registry keys | Do not copy older `Registry.BLOCK` / `Registry.ITEM` constants into 1.21.x code; those names are stale for the examples in this skill. ### ResourceLocation / Identifier Every registry entry needs a namespaced ID: ```java // NeoForge / vanilla Java ResourceLocation id = ResourceLocation.fromNamespaceAndPath("mymod", "my_block"); // Fabric with Yarn mappings Identifier id = Identifier.of("mymod", "my_block"); ``` --- ## 7. NeoForge Quick Patterns (26.x) For 26.x, use the explicitly labelled 26.x sections in `references/common-patterns.md` and select the project's exact version in the [NeoForge documentation](https://docs.neoforged.net/docs/gettingstarted/). `references/neoforge-api.md` contains legacy 1.21.x / Java 21 patterns only; do not copy its dependency pins into a 26.x project. ```java // Main mod class @Mod(MyMod.MOD_ID) public class MyMod { public static final String MOD_ID = "mymod"; public MyMod(IEventBus modEventBus) { ModBlocks.BLOCKS.register(modEventBus); ModItems.ITEMS.register(modEventBus); modEventBus.addListener(this::commonSetup); } private void commonSetup(FMLCommonSetupEvent event) { // runs after all mods are registered } } ``` ```java // Block registration public class ModBlocks { public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MyMod.MOD_ID); public static final DeferredBlock<Block> MY_BLOCK = BLOCKS.registerSimpleBlock("my_block", BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .strength(1.5f, 6.0f) .sound(SoundType.STONE) .requiresCorrectToolForDrops()); } ``` --- ## 8. Forge 1.20.1 Quick Patterns See full patterns in `references/forge-1.20.1-api.md`. ```java // Main mod class @Mod(MyMod.MOD_ID) public class MyMod { public static final String MOD_ID = "mymod"; public MyMod(FMLJavaModLoadingContext context) { IEventBus modEventBus = context.getModEventBus(); ModBlocks.BLOCKS.register(modEventBus); ModItems.ITEMS.register(modEventBus); modEventBus.addListener(this::commonSetup); MinecraftForge.EVENT_BUS.register(this); } private void commonSetup(FMLCommonSetupEvent event) { // runs after registries are prepared } } ``` ```java // Block registration public class ModBlocks { public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MyMod.MOD_ID); public static final RegistryObject<Block> MY_BLOCK = BLOCKS.register("my_block", () -> new Block( BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .strength(1.5f, 6.0f) .sound(SoundType.STONE) .requiresCorrectToolForDrops())); } ``` --- ## 9. Fabric Quick Patterns Match the project's Minecraft version and mappings in the [Fabric documentation](https://docs.fabricmc.net/develop/). `references/fabric-api.md` contains legacy 1.21.x / Java 21 patterns only. The explicitly labelled 26.x sections in `references/common-patterns.md` use NeoForge syntax; adapt them against the exact Fabric API rather than copying loader-specific classes or legacy dependency pins. ```java // Main mod class public class MyMod implements ModInitializer { public static final String MOD_ID = "mymod"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override public void onInitialize() { ModBlocks.initialize(); ModItems.register(); } } ``` ```java // Fabric 26.x with official Mojang mappings. Create the key before the object // so its properties receive the required id during construction. public final class ModBlocks { public static final ResourceKey<Block> MY_BLOCK_KEY = ResourceKey.create( Registries.BLOCK, Identifier.fromNamespaceAndPath(MyMod.MOD_ID, "my_block") ); public static final Block MY_BLOCK = register( MY_BLOCK_KEY, Block::new, BlockBehaviour.Properties.of() .mapColor(MapColor.STONE) .strength(1.5f, 6.0f) .sound(SoundType.STONE) .requiresCorrectToolForDrops() ); private static Block register(ResourceKey<Block> key, Function<BlockBehaviour.Properties, Block> factory, BlockBehaviour.Properties properties) { Block block = factory.apply(properties.setId(key)); return Registry.register(BuiltInRegistries.BLOCK, key, block); } public static void initialize() {} } ``` Call `ModBlocks.initialize()` from the Fabric initializer. The Yarn-named 1.21.11 examples remain in `references/fabric-api.md`; do not mix those names with this 26.x pattern. --- ## 10. JSON Asset Templates Always provide matching JSON assets for every registered block/item. Codex should generate or update these files alongside Java code. For Forge 1.20.1, check `references/forge-1.20.1-api.md` for legacy server-data directory names before creating loot tables or tags. See `references/common-patterns.md` for full JSON templates for: - Blockstate JSON - Block model JSON (cube, slab, stairs, fence, door, trapdoor, etc.) - Item model JSON - Loot table JSON - Recipe JSON (crafting_shaped, crafting_shapeless, smelting, blasting, stonecutting) - Language file (`en_us.json`) entries - Tag JSON --- ## 11. Data Generation Prefer data generation over hand-authored JSON for maintainability. ```java // NeoForge – register data gen providers in GatherDataEvent @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator gen = event.getGenerator(); PackOutput output = gen.getPackOutput(); ExistingFileHelper helper = event.getExistingFileHelper(); CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider(); gen.addProvider(event.includeClient(), new ModBlockStateProvider(output, helper)); gen.addProvider(event.includeClient(), new ModItemModelProvider(output, helper)); gen.addProvider(event.includeServer(), new ModRecipeProvider(output, lookupProvider)); gen.addProvider(event.includeServer(), new ModLootTableProvider(output, lookupProvider)); gen.addProvider(event.includeServer(), new ModBlockTagsProvider(output, lookupProvider, helper)); } ``` Run data generation with `./gradlew runData`, then commit the generated files. For Forge 1.20.1, use the mod-event-bus registration, `GatherDataEvent` signature, provider classes, and legacy output paths from `references/forge-1.20.1-api.md`. --- ## 12. Common Tasks Checklist When adding a **new block**: - [ ] `Block` subclass (or use vanilla Block with properties) - [ ] Register in `ModBlocks.BLOCKS` / `Registries.BLOCK` - [ ] Register `BlockItem` in `ModItems.ITEMS` / `Registries.ITEM` - [ ] Blockstate JSON → `assets/<modid>/blockstates/<name>.json` - [ ] Block model JSON → `assets/<modid>/models/block/<name>.json` - [ ] Item model JSON → `assets/<modid>/models/item/<name>.json` (or inherits from block) - [ ] 1.21.x item definition → `assets/<modid>/items/<name>.json`, pointing at the item or block model - [ ] Texture PNG → `assets/<modid>/textures/block/<name>.png` - [ ] Loot table JSON -> 1.21.x: `data/<modid>/loot_table/blocks/<name>.json`; Forge 1.20.1: `data/<modid>/loot_tables/blocks/<name>.json` - [ ] Tags -> 1.21.x: `data/<modid>/tags/block/` and `tags/item/`; Forge 1.20.1: `data/<modid>/tags/blocks/` and `tags/items/` - [ ] Language entry in `en_us.json` - [ ] Mine-with-correct-tool tag if hardness > 0 - [ ] Do not mix Forge 1.20.1 plural server-data paths with 1.21.x singular server-data paths When adding a **new item**: - [ ] `Item` subclass (or use `new Item(properties)`) - [ ] Register in `ModItems` / `Registries.ITEM` - [ ] Item model JSON - [ ] 1.21.x item definition → `assets/<modid>/items/<name>.json` - [ ] Texture PNG - [ ] Language entry - [ ] Creative tab registration (NeoForge/Forge: `BuildCreativeModeTabContentsEvent`; Fabric: `ItemGroupEvents`) - [ ] Recipe JSON if craftable (`data/<modid>/recipe/` for 26.x; see the version-specific recipe reference before using a 1.21.x project) When adding a **new entity**: - [ ] Entity class (extends appropriate base: `Mob`, `Animal`, `TamableAnimal`, etc.) - [ ] `EntityType` registration - [ ] Renderer class (`@OnlyIn(Dist.CLIENT)`) - [ ] Model class (`@OnlyIn(Dist.CLIENT)`) - [ ] Register renderer in `EntityRenderersEvent.RegisterRenderers` (NeoForge) or `EntityModelLayerRegistry` (Fabric) - [ ] Spawn egg item (optional) - [ ] Spawn rules / biome modifier --- ## 13. Open-Source Conventions - **License**: MIT or LGPL-3.0 — include `LICENSE` file and `SPDX-License-Identifier` header - **Versioning**: `{mod_version}+{mc_version}` (e.g., `2.0.0+1.21.11`) - **Changelog**: Keep `CHANGELOG.md` up to date with semver notes - **Publishing**: Use `gradle-modrinth` or `curseforgegradle` plugins for CurseForge / Modrinth - **CI**: GitHub Actions with `./gradlew build` and `./gradlew runGameTestServer` - **PR conventions**: Keep PRs scoped to a single feature; include asset files with Java changes --- ## 14. References - NeoForge API patterns and event system: `./references/neoforge-api.md` - Forge 1.20.1 API patterns and MDK workflow: `./references/forge-1.20.1-api.md` - Fabric API patterns and mixin guide: `./references/fabric-api.md` - Blocks, items, recipes, commands, GUIs, datagen: `./references/common-patterns.md` - NeoForge official docs: https://docs.neoforged.net/ - Fabric developer docs: https://docs.fabricmc.net/develop/ - Architectury (multiloader): https://docs.architectury.dev/ - Minecraft Wiki (data formats): https://minecraft.wiki/w/Java_Edition_data_values
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.