Skip to content

3.6 Implementing the Java Code Generator

Zhoumy303 edited this page Sep 27, 2026 · 1 revision

The goal of this task is to generate complete, compilable Java registration code for each item in the blueprint, supporting the basic, food, fuel, and tool types.
The code has been corrected according to the Minecraft 26.1+ API changes, including:

  • Tool classes now uniformly use Item.Properties methods such as .sword() and .axe(), instead of dedicated classes like SwordItem.
  • ApplyStatusEffectsConsumeEffect has been moved to the net.minecraft.world.item.consume_effects package.
  • Static field declaration order adjusted to avoid "illegal forward reference".
  • Fabric API package paths updated (CreativeModeTabEvents, etc.).
  • Fuel registration uses FuelValueEvents.

Prerequisites

  • Completed Task 5: modsmith/generator/java.py and the skeleton templates are ready.
  • Understood the blueprint structure: the items array, where each item has fields like id, type, display_name_en, etc.
  • The project already uses Minecraft 26.1+ dependencies and Java 25.

Step 1: Complete modsmith/templates/java/ModItemIds.java.jinja

package {{ package_name }};

import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;

public class ModItemIds {
{% for item in items %}
    public static final ResourceKey<Item> {{ item.id | upper }} = create("{{ item.id }}");
{% endfor %}

    private static ResourceKey<Item> create(String name) {
        return ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath("{{ mod_id }}", name));
    }
}

Step 2: Complete modsmith/templates/java/ModItems.java.jinja

package {{ package_name }};

import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.component.Consumable;
import net.minecraft.world.item.component.Consumables;
import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect;
import net.minecraft.world.item.ToolMaterial;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;

import java.util.List;
import java.util.function.Function;

public class ModItems {
{% for item in items %}
{% if item.type == "food" %}
    public static final FoodProperties {{ item.id | upper }}_FOOD = new FoodProperties.Builder()
        .nutrition({{ item.nutrition }})
        .saturationModifier({{ item.saturation }}f)
{% if item.always_edible %}
        .alwaysEdible()
{% endif %}
        .build();

    public static final Consumable {{ item.id | upper }}_CONSUMABLE = Consumables.defaultFood()
{% if item.effects %}
        .onConsume(new ApplyStatusEffectsConsumeEffect(List.of(
{% for effect in item.effects %}
            new MobEffectInstance(MobEffects.{{ effect.effect | upper }}, {{ effect.duration_ticks }}, {{ effect.amplifier }}){% if not loop.last %},{% endif %}
{% endfor %}
        ), 1.0f))
{% endif %}
        .build();
{% endif %}
{% if item.type == "tool" %}
    public static final ToolMaterial {{ item.id | upper }}_MATERIAL = new ToolMaterial(
        BlockTags.INCORRECT_FOR_IRON_TOOL,
        {{ item.durability }},
        {{ item.mining_speed }}f,
        {{ item.attack_damage }}f,
        22,
        ItemTags.IRON_TOOL_MATERIALS
    );
{% endif %}
{% endfor %}

{% for item in items %}
    public static final Item {{ item.id | upper }} = register(
        ModItemIds.{{ item.id | upper }},
{% if item.type == "basic" %}
        Item::new,
        new Item.Properties()
{% elif item.type == "food" %}
        Item::new,
        new Item.Properties().food({{ item.id | upper }}_FOOD, {{ item.id | upper }}_CONSUMABLE)
{% elif item.type == "fuel" %}
        Item::new,
        new Item.Properties()
{% elif item.type == "tool" %}
{% if item.tool_type == "sword" %}
        Item::new,
        new Item.Properties().sword({{ item.id | upper }}_MATERIAL, {{ item.attack_damage }}f, 1f)
{% elif item.tool_type == "axe" %}
        Item::new,
        new Item.Properties().axe({{ item.id | upper }}_MATERIAL, {{ item.attack_damage }}f, -3.0f)
{% elif item.tool_type == "pickaxe" %}
        Item::new,
        new Item.Properties().pickaxe({{ item.id | upper }}_MATERIAL, {{ item.attack_damage }}f, -2.8f)
{% elif item.tool_type == "shovel" %}
        Item::new,
        new Item.Properties().shovel({{ item.id | upper }}_MATERIAL, {{ item.attack_damage }}f, -3.0f)
{% elif item.tool_type == "hoe" %}
        Item::new,
        new Item.Properties().hoe({{ item.id | upper }}_MATERIAL, {{ item.attack_damage }}f, -2.0f)
{% endif %}
{% endif %}
    );
{% endfor %}

    public static Item register(ResourceKey<Item> itemKey, Function<Item.Properties, Item> itemFactory, Item.Properties settings) {
        Item item = itemFactory.apply(settings.setId(itemKey));
        return Registry.register(BuiltInRegistries.ITEM, itemKey, item);
    }

    public static void initialize() {
    }
}

Step 3: Complete modsmith/templates/java/ModItemsGenerated.java.jinja

package {{ package_name }};

import net.fabricmc.fabric.api.creativetab.v1.CreativeModeTabEvents;
import net.fabricmc.fabric.api.registry.FuelValueEvents;
import net.minecraft.world.item.CreativeModeTabs;

public class ModItemsGenerated {
    public static void initialize() {
        // Trigger ModItems class loading
        ModItems.initialize();

{% if items %}
        // Add items to the creative mode item group
        CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.INGREDIENTS)
            .register(entries -> {
{% for item in items %}
                entries.accept(ModItems.{{ item.id | upper }});
{% endfor %}
            });
{% endif %}
    }
}

Step 4: Ensure ExampleMod.java calls initialization

In modsmith/templates/fabric-project/src/main/java/com/example/ExampleMod.java, ensure onInitialize() calls ModItemsGenerated.initialize():

package {{ package_name }};

import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExampleMod implements ModInitializer {
    public static final String MOD_ID = "{{ mod_id }}";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);

    @Override
    public void onInitialize() {
        LOGGER.info("Hello Fabric world from {{ mod_name }}!");
        ModItemsGenerated.initialize();
    }
}

Step 5: Update modsmith/generator/java.py

"""Generate Java code related to item registration."""

from pathlib import Path

from jinja2 import Environment, FileSystemLoader

TEMPLATE_DIR = Path(__file__).parent.parent / "templates" / "java"


def generate_java_items(blueprint: dict, project_dir: Path) -> None:
    """Generate Java code for the items in the blueprint.

    Args:
        blueprint: Blueprint dictionary.
        project_dir: Root directory of the generated project.
    """
    env = Environment(
        loader=FileSystemLoader(str(TEMPLATE_DIR)),
        keep_trailing_newline=True,
    )

    context = {
        "package_name": blueprint["package_name"],
        "items": blueprint["items"],
        "mod_id": blueprint["mod_id"],
    }

    package_path = blueprint["package_name"].replace(".", "/")
    java_dir = project_dir / "src" / "main" / "java" / package_path
    java_dir.mkdir(parents=True, exist_ok=True)

    files_to_generate = {
        "ModItemIds.java.jinja": "ModItemIds.java",
        "ModItems.java.jinja": "ModItems.java",
        "ModItemsGenerated.java.jinja": "ModItemsGenerated.java",
    }

    for template_name, output_name in files_to_generate.items():
        template = env.get_template(template_name)
        content = template.render(**context)
        (java_dir / output_name).write_text(content, encoding="utf-8")

Step 6: End-to-end test

Create or reuse test_generator.py:

"""Test the project generator, including full Java code generation."""

from pathlib import Path

from modsmith.blueprint.validator import generate_validated_blueprint
from modsmith.generator.project import generate_project

# Use a blueprint containing multiple types for testing
blueprint = {
    "mod_id": "example-mod",
    "package_name": "com.example",
    "minecraft_version": "26.2",
    "fabric_loader_version": "0.19.5",
    "items": [
        {
            "id": "ruby",
            "type": "basic",
            "display_name_en": "Ruby",
            "display_name_zh": "红宝石",
            "texture": "auto"
        },
        {
            "id": "healing_apple",
            "type": "food",
            "display_name_en": "Healing Apple",
            "display_name_zh": "治愈苹果",
            "nutrition": 4,
            "saturation": 0.3,
            "always_edible": True,
            "effects": [
                {"effect": "regeneration", "duration_ticks": 200, "amplifier": 0}
            ],
            "texture": "auto"
        },
        {
            "id": "quark_gluon_plasma",
            "type": "fuel",
            "display_name_en": "Quark-Gluon Plasma",
            "display_name_zh": "夸克-胶子等离子体",
            "burn_time": 2400,
            "texture": "auto"
        },
        {
            "id": "guidite_sword",
            "type": "tool",
            "display_name_en": "Guidite Sword",
            "display_name_zh": "基迪特剑",
            "tool_type": "sword",
            "durability": 455,
            "mining_speed": 5.0,
            "attack_damage": 1.5,
            "texture": "auto"
        }
    ]
}

output_dir = Path("./generated_project")
generate_project(blueprint, output_dir)

Run:

python test_generator.py

Expected result: Under generated_project/src/main/java/com/example/:

  • ModItemIds.java: 4 ResourceKey constants.
  • ModItems.java: 4 item registrations; food has FoodProperties and Consumable; tool has ToolMaterial; all tools use the corresponding Item.Properties methods.
  • ModItemsGenerated.java: creative tab registration and fuel registration.

Step 7: Compile verification

cd generated_project
./gradlew build

If compilation passes, the Java code generator is working correctly. If there are still errors, adjust the imports or method calls in the templates according to the error messages.


Step 8: Task 6 Acceptance Criteria

  • ModItemIds.java.jinja generates a ResourceKey for each item.
  • ModItems.java.jinja supports the four types and generates correct, compilable registration code.
  • ModItemsGenerated.java.jinja generates creative tab registration and fuel registration.
  • ExampleMod.java calls ModItemsGenerated.initialize().
  • java.py can render all templates and write them to the correct paths.
  • The generated_project produced by test_generator.py compiles via ./gradlew build.

After completing this task, your ModSmith will be able to generate fully functional Java item code. Next, you can proceed to Task 7 (refine the resource file generator), Task 8 (texture generator), and Task 9 (compile verification module).

Clone this wiki locally