Skip to content

Recipe System Integration ​

This chapter introduces how to integrate and extend AnvilCraft's recipe system in your Addon.

Overview ​

AnvilCraft provides a rich set of recipe types for implementing various processing and transformation functions. Addon developers can:

  1. Use existing recipe types - Add new recipes via datapacks or code
  2. Create custom recipe types - Define entirely new recipe processing logic
  3. Integrate InWorld recipes - Use AnvilLib's in-world recipe system

AnvilCraft Recipe Types ​

AnvilCraft provides the following recipe types:

Recipe TypeRegistry NamePurpose
Item Crushanvilcraft:item_crushCrush items into smaller items
Item Compressanvilcraft:item_compressCompress multiple items into advanced items
Block Crushanvilcraft:block_crushCrush blocks into smaller blocks/items
Block Compressanvilcraft:block_compressCompress multiple blocks into advanced blocks
Stampinganvilcraft:stampingTransform items on the stamping platform
Unpackanvilcraft:unpackUnpack compressed items
Bulginganvilcraft:bulgingBulge items using fluid
Squeezinganvilcraft:squeezingSqueeze blocks using fluid
Item Injectanvilcraft:item_injectInject fluid into items
Cookinganvilcraft:cookingCook items using heat sources
Super Heatinganvilcraft:super_heatingHeat using high-temperature fluid
Time Warpanvilcraft:time_warpTime power transformation
Block Smearanvilcraft:block_smearSmear using blocks
Mob Transformanvilcraft:mob_transformTransform mob entities
Multiblockanvilcraft:multiblockMultiblock structure recipes
Jewel Craftinganvilcraft:jewel_craftingJewel crafting
Mineral Fountainanvilcraft:mineral_fountainMineral fountain transformation

Adding Recipes via Datapacks ​

The simplest way is to add recipes via datapacks. Create recipe JSON files in your mod resources.

Recipe File Location ​

src/main/resources/data/<modid>/recipe/<recipe_name>.json

Item Crush Recipe Example ​

json
{
  "type": "anvilcraft:item_crush",
  "ingredients": [
    {
      "items": "myaddon:custom_ore"
    }
  ],
  "results": [
    {
      "id": "myaddon:custom_dust",
      "count": 2
    }
  ]
}

Item Compress Recipe Example ​

json
{
  "type": "anvilcraft:item_compress",
  "ingredients": [
    {
      "items": "myaddon:custom_dust",
      "count": 9
    }
  ],
  "results": [
    {
      "id": "myaddon:custom_block",
      "count": 1
    }
  ]
}

Bulging Recipe Example ​

json
{
  "type": "anvilcraft:bulging",
  "ingredients": [
    {
      "items": "myaddon:dry_sponge"
    }
  ],
  "results": [
    {
      "id": "minecraft:wet_sponge",
      "count": 1
    }
  ],
  "fluid": "minecraft:water",
  "consume": 1
}

Fluid recipes (bulging, squeezing, super heating, time warp) support the following fields:

FieldTypeDescription
fluidStringRequired fluid type
transformStringFluid type after transformation (optional)
consumeintFluid consumption amount, positive for consume, negative for produce

Adding Recipes via Code ​

Using Data Generators ​

It's recommended to use data generators to generate recipe files at compile time:

java
public class MyAddonRecipeProvider extends RecipeProvider {
    
    public MyAddonRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) {
        super(registries, output);
    }
    
    @Override
    protected void buildRecipes() {
        // Item crush recipe
        ItemCrushRecipe.builder()
            .requires(ModItems.CUSTOM_ORE.get())
            .result(ModItems.CUSTOM_DUST.get(), 2)
            .save(this.output, MyAddon.of("item_crush/custom_ore"));
        
        // Item compress recipe
        ItemCompressRecipe.builder()
            .requires(ModItems.CUSTOM_DUST.get(), 9)
            .result(ModBlocks.CUSTOM_BLOCK.get().asItem())
            .save(this.output, MyAddon.of("item_compress/custom_dust"));
    }
}

Adding Recipes at Runtime ​

If you need to dynamically add recipes, you can listen to the AddReloadListenerEvent:

java
@Mod.EventBusSubscriber(modid = MyAddon.MOD_ID, bus = Mod.EventBusSubscriber.Bus.GAME)
public class RecipeEventHandler {
    
    @SubscribeEvent
    public static void onAddReloadListener(AddReloadListenerEvent event) {
        // Access RecipeManager
        RecipeManager recipeManager = event.getServerResources().getRecipeManager();
        // You can handle recipe-related logic here
    }
}

InWorld Recipe System ​

AnvilLib provides a powerful InWorld recipe system for handling in-world interaction recipes.

InWorld Recipe Structure ​

json
{
  "type": "anvillib_recipe:in_world_recipe",
  "icon": {
    "item": "minecraft:anvil"
  },
  "trigger": "anvilcraft:on_anvil_fall_on",
  "conflicting": [],
  "non_conflicting": [],
  "outcomes": [],
  "priority": 0,
  "compatible": true,
  "max_efficiency": 2147483647
}

Field Description ​

FieldTypeDescription
typeStringFixed as anvillib_recipe:in_world_recipe
iconObjectRecipe icon
triggerStringTrigger type
conflictingArrayList of conflicting predicates
non_conflictingArrayList of non-conflicting predicates
outcomesArrayList of recipe outcomes
priorityintPriority, higher values are processed first
compatiblebooleanCompatibility mode
max_efficiencyintMaximum efficiency value

Trigger Types ​

  • anvilcraft:on_anvil_fall_on - Triggers when an anvil falls

Predicate Types ​

Predicates are used to check recipe execution conditions:

anvillib_recipe:has_item ​

Check if an item exists at the specified position:

json
{
  "type": "anvillib_recipe:has_item",
  "offset": [0, -1, 0],
  "item": {
    "items": "minecraft:iron_ingot"
  }
}

anvillib_recipe:has_item_ingredient ​

Check for item and consume on match:

json
{
  "type": "anvillib_recipe:has_item_ingredient",
  "offset": [0, -1, 0],
  "item": {
    "items": "minecraft:iron_ingot"
  }
}

anvillib_recipe:has_block ​

Check if a block exists at the specified position:

json
{
  "type": "anvillib_recipe:has_block",
  "offset": [0, -1, 0],
  "block": {
    "blocks": "minecraft:iron_block"
  }
}

Outcome Types ​

Outcomes define the effects when the recipe is executed:

anvillib_recipe:spawn_item ​

Spawn items at the specified position:

json
{
  "type": "anvillib_recipe:spawn_item",
  "offset": [0, -1, 0],
  "item": {
    "id": "minecraft:iron_nugget",
    "count": 9
  }
}

anvillib_recipe:set_block ​

Set a block at the specified position:

json
{
  "type": "anvillib_recipe:set_block",
  "offset": [0, -1, 0],
  "block": {
    "name": "minecraft:air"
  }
}

Complete InWorld Recipe Example ​

Convert iron ingot to iron nuggets by crushing:

json
{
  "type": "anvillib_recipe:in_world_recipe",
  "icon": {
    "item": "minecraft:iron_nugget"
  },
  "trigger": "anvilcraft:on_anvil_fall_on",
  "conflicting": [],
  "non_conflicting": [
    {
      "type": "anvillib_recipe:has_item_ingredient",
      "offset": [0, -1, 0],
      "item": {
        "items": "minecraft:iron_ingot"
      }
    }
  ],
  "outcomes": [
    {
      "type": "anvillib_recipe:spawn_item",
      "offset": [0, -1, 0],
      "item": {
        "id": "minecraft:iron_nugget",
        "count": 9
      }
    }
  ],
  "priority": 0,
  "compatible": true
}

Creating Custom Recipe Types ​

If existing recipe types don't meet your needs, you can create custom recipe types.

1. Define Recipe Class ​

java
package com.example.myaddon.recipe;

import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeInput;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;

public class CustomRecipe implements Recipe<RecipeInput> {
    private final ItemStack input;
    private final ItemStack output;
    
    public CustomRecipe(ItemStack input, ItemStack output) {
        this.input = input;
        this.output = output;
    }
    
    @Override
    public boolean matches(RecipeInput input, Level level) {
        // Implement matching logic
        return ItemStack.isSameItemSameComponents(
            input.getItem(0), this.input
        );
    }
    
    @Override
    public ItemStack assemble(RecipeInput input, HolderLookup.Provider registries) {
        return this.output.copy();
    }
    
    @Override
    public RecipeSerializer<? extends Recipe<RecipeInput>> getSerializer() {
        return ModRecipeTypes.CUSTOM_SERIALIZER.get();
    }
    
    @Override
    public RecipeType<? extends Recipe<RecipeInput>> getType() {
        return ModRecipeTypes.CUSTOM_TYPE.get();
    }
    
    // Other required methods...
}

2. Define Recipe Serializer ​

java
public class CustomRecipe {
    // ... Recipe class content
    
    public static class Serializer implements RecipeSerializer<CustomRecipe> {
        private static final MapCodec<CustomRecipe> CODEC = RecordCodecBuilder.mapCodec(
            instance -> instance.group(
                ItemStack.CODEC.fieldOf("input").forGetter(r -> r.input),
                ItemStack.CODEC.fieldOf("output").forGetter(r -> r.output)
            ).apply(instance, CustomRecipe::new)
        );
        
        private static final StreamCodec<RegistryFriendlyByteBuf, CustomRecipe> STREAM_CODEC =
            StreamCodec.composite(
                ItemStack.STREAM_CODEC, r -> r.input,
                ItemStack.STREAM_CODEC, r -> r.output,
                CustomRecipe::new
            );
        
        @Override
        public MapCodec<CustomRecipe> codec() {
            return CODEC;
        }
        
        @Override
        public StreamCodec<RegistryFriendlyByteBuf, CustomRecipe> streamCodec() {
            return STREAM_CODEC;
        }
    }
}

3. Register Recipe Type ​

java
package com.example.myaddon.init;

import com.example.myaddon.MyAddon;
import com.example.myaddon.recipe.CustomRecipe;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;

public class ModRecipeTypes {
    private static final DeferredRegister<RecipeType<?>> RECIPE_TYPES =
        DeferredRegister.create(Registries.RECIPE_TYPE, MyAddon.MOD_ID);
    private static final DeferredRegister<RecipeSerializer<?>> RECIPE_SERIALIZERS =
        DeferredRegister.create(Registries.RECIPE_SERIALIZER, MyAddon.MOD_ID);
    
    // Register recipe type
    public static final DeferredHolder<RecipeType<?>, RecipeType<CustomRecipe>> CUSTOM_TYPE =
        RECIPE_TYPES.register("custom", () -> new RecipeType<>() {
            @Override
            public String toString() {
                return MyAddon.MOD_ID + ":custom";
            }
        });
    
    // Register recipe serializer
    public static final DeferredHolder<RecipeSerializer<?>, RecipeSerializer<CustomRecipe>> CUSTOM_SERIALIZER =
        RECIPE_SERIALIZERS.register("custom", CustomRecipe.Serializer::new);
    
    public static void register(IEventBus bus) {
        RECIPE_TYPES.register(bus);
        RECIPE_SERIALIZERS.register(bus);
    }
}

4. Register in Main Class ​

java
@Mod(MyAddon.MOD_ID)
public class MyAddon {
    public MyAddon(IEventBus modEventBus) {
        ModRecipeTypes.register(modEventBus);
        // Other initialization...
    }
}

Recipe Data Generation ​

Using data generators can automatically generate recipe JSON files.

Recipe Provider Example ​

java
public class ModRecipeProvider extends RecipeProvider {
    
    public ModRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) {
        super(registries, output);
    }
    
    @Override
    protected void buildRecipes() {
        // Generate standard crafting recipe
        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.CUSTOM_ITEM.get())
            .pattern("III")
            .pattern("IAI")
            .pattern("III")
            .define('I', Items.IRON_INGOT)
            .define('A', Items.AMETHYST_SHARD)
            .unlockedBy("has_iron", has(Items.IRON_INGOT))
            .save(this.output);
        
        // Generating AnvilCraft recipes requires using corresponding Builders
        // or directly generating JSON data
    }
}

Best Practices ​

  1. Prefer Datapacks

    • Datapack recipes are easier to maintain and modify
    • Support resource pack overrides
  2. Set Priorities Appropriately

    • InWorld recipe priority determines execution order
    • Avoid recipe conflicts
  3. Use Tags

    • Use item/block tags for better recipe compatibility
    • Facilitates integration with other mods
  4. Test Recipes

    • Use /reload command to reload recipes
    • Verify all recipe paths are correct
  5. Documentation

    • Provide usage instructions for custom recipes
    • Document recipe prerequisites

Released under the CC-BY-NC-SA 4.0 License.