After we created a Basic Container Block that stores items in the last part, we will now create a basic furnace block. Our block will then be able to smelt items similar to a regular vanilla furnace. In the next part we will then go the next step and create a custom recipe type for our block. We will be using the Ruby Furnace we added in the Directional Blocks tutorial.
Additional Resources
The Tutorial Mod used here can be found on GitHub (state after this tutorial).
A video version can be found below.
Contents
Creating the Furnace BlockEntity
In the first step we need to create and register a blockentity for our Ruby Furnace. Thankfully Minecraft provides us with the AbstractFurnaceBlockEntity class, which already handles most of the furnace-related stuff for us. Accordingly we need to create a new class extending AbstractFurnaceBlockEntity and implement a few methods:
public class RubyFurnaceEntity extends AbstractFurnaceBlockEntity {
public RubyFurnaceEntity(BlockPos pos, BlockState blockState) {
super(TutorialModBlockEntityTypes.RUBY_FURNACE.get(), pos, blockState, RecipeType.SMELTING);
}
@Override
protected Component getDefaultName() {
return Component.translatable("container.tutorial_mod.ruby_furnace");
}
@Override
protected AbstractContainerMenu createMenu(int containerId, Inventory inventory) {
return new FurnaceMenu(containerId, inventory, this, this.dataAccess);
}
}
In the constructor we pass through the BlockPos and the BlockState. Additionally we need to provide our BlockEntityType, which we will register shortly, and the RecipeType this furnace should be able to process, in this case SMELTING, which are the recipes used by a regular furnace. Furthermore we need to implement the getDefaultName() method, through which we provide the name displayed at the top of the furnace GUI. We also need to implement the createMenu() method. Since we just want a regular furnace GUI, we can create a new FurnaceMenu here, passing through the containerId and the players inventory. Additionally we need to pass the furnace container, represented by an instance of our BlockEntity, and the dataAccess provided to us by the AbstractFurnaceBlockEntity class.
Next we need to register our BlockEntityType. As explained in the tutorial on Basic BlockEntities, this is a bit more complicated since we need modloader specific code. Check out the linked article for more information. We need to add the following lines in our BlockEntityType classes:
//Common BlockEntityTypes class:
public static RegistrySupplier<BlockEntityType<RubyFurnaceEntity>> RUBY_FURNACE;
//Fabric BlockEntityTypes class:
public static void init(){
...
RUBY_FURNACE = registerBlockEntity("ruby_furnace", () ->
FabricBlockEntityTypeBuilder.create(RubyFurnaceEntity::new, TutorialModBlocks.RUBY_FURNACE.get()).build()
);
...
}
//NeoForge BlockEntityTypes class:
public static void init(){
...
RUBY_FURNACE = registerBlockEntity("ruby_furnace", () ->
new BlockEntityType<>(RubyFurnaceEntity::new, TutorialModBlocks.RUBY_FURNACE.get())
);
...
}
With that we now have a BlockEntity for our Ruby Furnace together with a registered BlockEntityType.
Block Class
Next we need to create the block class for our Ruby Furnace. If you already created it as shown in an earlier tutorial, you need to change the superclass, update the type in the codec() return value and remove all methods related to block states, since they are already implemented in the superclass.
For our block class Minecraft again provides us with a helpful superclass to use: AbstractFurnaceBlock. This class already implements lots of required stuff, like the BlockState handling for rotations or the LIT property, determining whether our furnace should appear on. Additionally we want to implement, similar to our Ruby Crate, the RightClickBlock interaction event. We then need to implement a few required and a few technically optional methods, although those allow our Ruby Furnace to function properly in the first place. The resulting block class is this, explained below:
public class RubyFurnaceBlock extends AbstractFurnaceBlock implements InteractionEvent.RightClickBlock {
public static final MapCodec<RubyFurnaceBlock> CODEC = simpleCodec(RubyFurnaceBlock::new);
@Override
protected @NotNull MapCodec<? extends AbstractFurnaceBlock> codec() {
return CODEC;
}
protected RubyFurnaceBlock(Properties properties) {
super(properties);
InteractionEvent.RIGHT_CLICK_BLOCK.register(this);
}
@Override
protected void openContainer(Level level, BlockPos pos, Player player) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof AbstractFurnaceBlockEntity) {
player.openMenu((MenuProvider)blockEntity);
player.awardStat(Stats.INTERACT_WITH_FURNACE);
}
}
@Override
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(
Level level, BlockState state, BlockEntityType<T> blockEntityType) {
return createFurnaceTicker(level, blockEntityType, TutorialModBlockEntityTypes.RUBY_FURNACE.get());
}
@Override
public InteractionResult click(Player player, InteractionHand interactionHand, BlockPos blockPos, Direction direction) {
if(player.level().getBlockEntity(blockPos) == null
|| !(player.level().getBlockEntity(blockPos) instanceof RubyFurnaceEntity blockEntity)){
return InteractionResult.PASS;
}
if(player.isShiftKeyDown()){
return InteractionResult.PASS;
}
player.openMenu(blockEntity);
return InteractionResult.SUCCESS;
}
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new RubyFurnaceEntity(pos, state);
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
if (state.getValue(LIT)) {
double d = (double)pos.getX() + (double)0.5F;
double e = pos.getY();
double f = (double)pos.getZ() + (double)0.5F;
if (random.nextDouble() < 0.1) {
level.playLocalSound(d, e, f, SoundEvents.FURNACE_FIRE_CRACKLE, SoundSource.BLOCKS, 1.0F, 1.0F, false);
}
Direction direction = state.getValue(FACING);
Direction.Axis axis = direction.getAxis();
double g = 0.52;
double h = random.nextDouble() * 0.6 - 0.3;
double i = axis == Direction.Axis.X ? (double)direction.getStepX() * 0.52 : h;
double j = random.nextDouble() * (double)6.0F / (double)16.0F;
double k = axis == Direction.Axis.Z ? (double)direction.getStepZ() * 0.52 : h;
level.addParticle(ParticleTypes.SMOKE, d + i, e + j, f + k, (double)0.0F, (double)0.0F, (double)0.0F);
level.addParticle(ParticleTypes.FLAME, d + i, e + j, f + k, (double)0.0F, (double)0.0F, (double)0.0F);
}
}
}
Since the first part is similar to the Ruby Crate, check its tutorial for a detailed explanation. The first relevant method for us is the openContainer() method. In here we check whether a AbstractFurnaceBlockEntity exists at the given BlockPos. If yes we open the menu of said BlockEntity for the player. After that we award the player the stat that he interacted with a furnace. If you don’t want this, you can leave this line out.
Next we also implement the getTicker() method. As explained in the BlockEntity Basics a Ticker is a piece of code that is run every tick to check for changes for example. In case of a furnace block, this includes checking if a correct recipe is inside the container. Therefore we need a ticker for our furnace to work properly. Thankfully the AbstractFurnaceBlock already provides us with a method to create a ticker: createFurnaceTicker(). Here we just need to pass the level and state properties together with our BlockEntityType.
We also need to implement the click() method of our RightClickBlock interaction event. We perform similar checks to the Ruby Crate to see if we have a correct BlockEntity and then open the menu. See the linked tutorial for more details on this.
Furthermore we need to implement the newBlockEntity() method, returning a new instance of the BlockEntity class we created above.
If you want to have the same particle and sound effects as a vanilla furnace, you can also override the animateTick() method as shown above. The contents given here are just a copy of the regular furnace animation, but this is entirely optional.
BlockStates and Model
The last thing we need to update are the model and blockstate information for our block. As shown in the Directional Blocks tutorial we created a model for the unlit version of our Ruby Furnace together with a blockstate file using the facing property. We now need to create an additional model for the lit state of our Ruby Furnace, looking like this:
{
"parent": "minecraft:block/orientable",
"textures": {
"front": "tutorial_mod:block/ruby_furnace_front_lit",
"side": "tutorial_mod:block/ruby_furnace_side",
"top": "tutorial_mod:block/ruby_furnace_top"
}
}
We then want to use this new lit model when our block has the lit=true blockstate, so we need to add this to our blockstates file, which then looks like this:
{
"variants": {
"facing=east,lit=false": {
"model": "tutorial_mod:block/ruby_furnace",
"y": 90
},
"facing=north,lit=false": {
"model": "tutorial_mod:block/ruby_furnace"
},
"facing=south,lit=false": {
"model": "tutorial_mod:block/ruby_furnace",
"y": 180
},
"facing=west,lit=false": {
"model": "tutorial_mod:block/ruby_furnace",
"y": 270
},
"facing=east,lit=true": {
"model": "tutorial_mod:block/ruby_furnace_lit",
"y": 90
},
"facing=north,lit=true": {
"model": "tutorial_mod:block/ruby_furnace_lit"
},
"facing=south,lit=true": {
"model": "tutorial_mod:block/ruby_furnace_lit",
"y": 180
},
"facing=west,lit=true": {
"model": "tutorial_mod:block/ruby_furnace_lit",
"y": 270
}
}
}
Minecraft
With that done we can launch the game, place down our block and put items for a valid furnace recipe inside of it. We can then observe our block changing the front texture, spawning particles and sound effects and processing the recipe inside of it:


With that we have created a basic furnace block using Architectury. In the next part we will add a custom RecipeType and our own recipes for it.
Video
Please accept YouTube cookies to play this video. By accepting you will be accessing content from YouTube, a service provided by an external third party.
If you accept this notice, your choice will be saved and the page will refresh.
Related Content
Check out the entire series here.
Pingback: Recipe Type (Furnace) - Architectury API Multiplatform Modding - Larsen's Mods