Claude Skill

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.

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download Jahrome907-minecraft-agent-skills-.claude_skills_minecraft-modding-dd57c5a.zip · 25 KB
Part of jahrome907/minecraft-agent-skills — 52 skills

Install

skills CLI npx skills add https://github.com/Jahrome907/minecraft-agent-skills/tree/main/.claude/skills/minecraft-modding
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jahrome907-minecraft-agent-skills@llmmart
Git 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 @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

# 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:

  • 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

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.

No comments yet.

Reviews (0)

No reviews yet.

Related