This page needs some revisions
Creating a cooking recipe type is just like creating a custom recipe type with extra steps. Vanilla minecraft abstracts classes for furnaces and cooking recipe types so we'll use them to save some time and code! If it is your first time creating a container block, you should go check this tutorial before: screenhandler
The final result of this tutorial can be found here: https://github.com/Legorel/CookingRecipeExample
First we need our furnace, for this our class will extends AbstractFurnaceBlock
. Some of the classes will be created later :
// Do not forget that we need every class to extends the abstract furnace classes or it won't work! public class TestFurnace extends AbstractFurnaceBlock { public TestFurnace(Settings settings) { super(settings); } @Override public @Nullable BlockEntity createBlockEntity(BlockView world) { return new TestFurnaceBlockEntity(); } @Override public void openScreen(World world, BlockPos pos, PlayerEntity player) { //This is called by the onUse method inside AbstractFurnaceBlock so //it is a little bit different of how you open the screen for normal container BlockEntity blockEntity = world.getBlockEntity(pos); if (blockEntity instanceof TestFurnaceBlockEntity) { player.openHandledScreen((NamedScreenHandlerFactory)blockEntity); // Optional: increment player's stat player.incrementStat(Stats.INTERACT_WITH_FURNACE); } } }
Now we need or BlockEntity, some classes will be created later :
public class TestFurnaceBlockEntity extends AbstractFurnaceBlockEntity { //Since we already now the BlockEntityType and RecipeType we don't need them in the constructor's parameters public TestFurnaceBlockEntity() { super(CookingRecipeExample.TEST_FURNACE_BLOCK_ENTITY, CookingRecipeExample.TEST_RECIPE_TYPE); } @Override public Text getContainerName() { //you should use a translation key instead but this is easier return Text.of("test furnace"); } @Override public ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory) { return new TestFurnaceScreenHandler(syncId, playerInventory, this, this.propertyDelegate); } }
Now we need to register or Block
, BlockItem
and BlockEntity
:
public class CookingRecipeExample implements ModInitializer { public static final String MOD_ID = "cookingrecipeexample"; public static final Block TEST_FURNACE_BLOCK; public static final BlockEntityType TEST_FURNACE_BLOCK_ENTITY; static { //Block TEST_FURNACE_BLOCK = Registry.register(Registry.BLOCK, new Identifier(MOD_ID, "test_furnace"), new TestFurnace(FabricBlockSettings.of(Material.METAL))); //BlockItem Registry.register(Registry.ITEM, new Identifier(MOD_ID, "test_furnace"), new BlockItem(TEST_FURNACE_BLOCK, new Item.Settings().group(ItemGroup.DECORATIONS))); //BlockEntity TEST_FURNACE_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "test_furnace"), BlockEntityType.Builder.create(TestFurnaceBlockEntity::new, TEST_FURNACE_BLOCK).build(null)); } @Override public void onInitialize() { } }
Now we will create the recipe class, again we need to extend some vanilla classes :
public class TestRecipe extends AbstractCookingRecipe { //Same for the BlockEntity, we don't need some of the parameters in the constructor since we already now the type public TestRecipe(Identifier id, String group, Ingredient input, ItemStack output, float experience, int cookTime) { super(CookingRecipeExample.TEST_RECIPE_TYPE, id, group, input, output, experience, cookTime); } @Override public ItemStack getRecipeKindIcon() { return new ItemStack(Items.BLACKSTONE); } @Override public RecipeSerializer<?> getSerializer() { //The Serializer will be created later return CookingRecipeExample.TEST_RECIPE_SERIALIZER; } }
Then we need to register it :
public class CookingRecipeExample implements ModInitializer { [...] public static final RecipeType<TestRecipe> TEST_RECIPE_TYPE; static { [...] TEST_RECIPE_TYPE = Registry.register(Registry.RECIPE_TYPE, new Identifier(MOD_ID, "test_furnace"), new RecipeType<TestRecipe>() { @Override public String toString() {return "test_furnace";} }); } }
This is where it gets different from a normal recipe type, you can make your own serializer if your furnace works differently from vanilla one or just register a new CookingRecipeSerializer
to save some code :
public class CookingRecipeExample implements ModInitializer { [...] public static final RecipeSerializer<TestRecipe> TEST_RECIPE_SERIALIZER; static { [...] TEST_RECIPE_SERIALIZER = Registry.register(Registry.RECIPE_SERIALIZER, new Identifier(MODID, "test_furnace"), new CookingRecipeSerializer<>(TestRecipe::new, 200)); } }
There is just one problem, CookingRecipeSerializer
uses an interface RecipeFactory
that we don't have access to. We could just make our own serializer but this will mean that we couldn't use the classes like AbstractFurnaceBlockEntity
because of some Generic Types
. To counter this we will use an Access Widener
to get access to the interface. The tutorial on Access Widener
can be found here : accesswideners
AW path : .../src/main/resources/modid.accesswidener
accessWidener v1 named accessible class net/minecraft/recipe/CookingRecipeSerializer$RecipeFactory
Now we need to add our AW to fabric.mod.json
and build.gradle
fabric.mod.json: .../src/main/resources/fabric.mod.json
{ [...], "accessWidener": "cookingrecipeexample.accesswidener" }
minecraft { accessWidener "src/main/resources/cookingrecipeexample.accesswidener" }
After that you need to reload gradle changes and the RecipeFactory
interface should now be public !
Now we need to create our screen handler so the furnace's inventory can work. We still need to emove some of the constructor's parameters since we already now their value :
public class TestFurnaceScreenHandler extends AbstractFurnaceScreenHandler { public TestFurnaceScreenHandler(int i, PlayerInventory playerInventory) { super(CookingRecipeExample.TEST_FURNACE_SCREEN_HANDLER, CookingRecipeExample.TEST_RECIPE_TYPE, RecipeBookCategory.FURNACE, i, playerInventory); } public TestFurnaceScreenHandler(int i, PlayerInventory playerInventory, Inventory inventory, PropertyDelegate propertyDelegate) { super(CookingRecipeExample.TEST_FURNACE_SCREEN_HANDLER, CookingRecipeExample.TEST_RECIPE_TYPE, RecipeBookCategory.FURNACE, i, playerInventory, inventory, propertyDelegate); } }
Unfortunately, there is no easy way to create a custom recipe book screen since they are hard-coded in Enums
. We'll use the furnace recipe book screen instead.
Then we register it :
public class CookingRecipeExample implements ModInitializer { [...] public static final ScreenHandlerType<TestFurnaceScreenHandler> TEST_FURNACE_SCREEN_HANDLER; static { [...] TEST_FURNACE_SCREEN_HANDLER = ScreenHandlerRegistry.registerSimple(new Identifier(MOD_ID, "test_furnace"), TestFurnaceScreenHandler::new); } }
The last thing we need to do is add the Screen
, we will need to register it in the client-side:
@Environment(EnvType.CLIENT) public class TestFurnaceScreen extends AbstractFurnaceScreen<TestFurnaceScreenHandler> { //You can replace the background with whatever you like, just remember there will always be the recipe book button private static final Identifier BACKGROUND = new Identifier("textures/gui/container/furnace.png"); public TestFurnaceScreen(TestFurnaceScreenHandler handler, PlayerInventory inventory, Text title) { super(handler, new FurnaceRecipeBookScreen(), inventory, title, BACKGROUND); } }
And we register it:
@Environment(EnvType.CLIENT) public class CookingRecipeExampleClient implements ClientModInitializer { @Override public void onInitializeClient() { ScreenRegistry.register(CookingRecipeExample.TEST_FURNACE_SCREEN_HANDLER, TestFurnaceScreen::new); } }
Then we add our ClientModInitializer
in the fabric.mod.json
file, just change the package to yours:
"entrypoints": { "main": [ [...] ], "client": [ "fr.legorel.cookingrecipeexample.client.CookingRecipeExampleClient" ], "server": [] },
Now it is the time to test our recipe type, before continuing you should make sure that every class we created is imported in the classes where they are used. To create our recipe we simply follow the official way: https://minecraft.gamepedia.com/Recipe#JSON_format
Recipe path: .../src/main/resources/data/modid/recipes/test_recipe.json
{ "type": "cookingrecipeexample:test_furnace", "ingredient": { "item": "minecraft:stone" }, "result": "minecraft:diamond", "experience": 0.2 }
And finally, we can run our client and test our new furnace and recipe type:
We have our recipe type, our furnace, but no textures for our block. You can learn how to add visuals to your block here : giving_your_block_visuals
But don't forget that we extended AbstractFurnaceBlock
, this mean our block already have 2 BlockState
, one for the direction it is facing and the other for when it is lit or not. You can find a tutorial on BlockStates
here: adding_models_for_your_blockstates
Just in case, i'll put the furnace blockstates file as an example:
{ "variants": { "facing=east,lit=false": { "model": "minecraft:block/furnace", "y": 90 }, "facing=east,lit=true": { "model": "minecraft:block/furnace_on", "y": 90 }, "facing=north,lit=false": { "model": "minecraft:block/furnace" }, "facing=north,lit=true": { "model": "minecraft:block/furnace_on" }, "facing=south,lit=false": { "model": "minecraft:block/furnace", "y": 180 }, "facing=south,lit=true": { "model": "minecraft:block/furnace_on", "y": 180 }, "facing=west,lit=false": { "model": "minecraft:block/furnace", "y": 270 }, "facing=west,lit=true": { "model": "minecraft:block/furnace_on", "y": 270 } } }