A New Way to Feed Butterflies

The next step in the butterflies mod is to integrate them with villagers. Before I can do that they need a job block of some kind. So I’ve created a block that can help feed and breed butterflies. Later, we can use this block to create butterfly related villagers.

GUI


The first step in creating the butterfly feeder was to create a GUI for the block. This is what will display when the player right-clicks on the block, allowing them to interact with the block.

The most basic component of a menu is a slot. This is what allows you to move items to and from a container of any kind, be it a chest, furnace, or dispenser. Basic slots allow any item in them, but if you want to limit the items that can go in one, you need to create a custom slot.

/**
 * A slot for the butterfly feeder that limits what can be placed in it.
 */
public class ButterflyFeederSlot extends Slot {

    /**
     * Construction.
     * @param container The container.
     * @param slotIndex The slot index.
     * @param xPos The screen coordinates for the slot.
     * @param yPos The screen coordinates for the slot.
     */
    public ButterflyFeederSlot(Container container,
                               int slotIndex,
                               int xPos,
                               int yPos) {
        super(container, slotIndex, xPos, yPos);
    }

    /**
     * Tells the game if the item may be placed in the slot.
     * @param itemStack The item stack to test.
     * @return TRUE if the item is some kind of butterfly food.
     */
    @Override
    public boolean mayPlace(ItemStack itemStack) {
        return itemStack.is(ItemTags.SMALL_FLOWERS) ||
                itemStack.is(Items.APPLE) ||
                itemStack.is(Items.SWEET_BERRIES) ||
                itemStack.is(Items.MELON_SLICE);
    }
}

By overriding mayPlace() we can limit the items that can go into the slot to only the items that can be used to feed butterflies and moths.

Now that we have a custom slot, we can create the menu. This class tells Minecraft what slots are available and how they are arranged on the screen when the UI is displayed. For our GUI, we will add a single slot for the butterfly feeder, and all the slots for the player’s inventory and hotbar as well.

/**
 * The menu for a butterfly feeder.
 */
public class ButterflyFeederMenu extends AbstractContainerMenu {

    // The container the menu is interfacing with.
    private final Container feeder;

    /**
     * Client constructor.
     * @param menuType The type of this menu.
     * @param containerId The ID of the container.
     * @param playerInventory The player's inventory.
     */
    public ButterflyFeederMenu(MenuType<?> menuType,
                               int containerId,
                               Inventory playerInventory) {
        this(menuType, containerId, playerInventory, new SimpleContainer(1));
    }

    /**
     * Server constructor.
     * @param menuType The type of this menu.
     * @param containerId The ID of the container.
     * @param playerInventory The player's inventory.
     * @param container The container for the feeder.
     */
    public ButterflyFeederMenu(MenuType<?> menuType,
                               int containerId,
                               Inventory playerInventory,
                               Container container) {
        super(menuType, containerId);

        this.feeder = container;
        checkContainerSize(container, 1);
        container.startOpen(playerInventory.player);

        this.addSlot(new ButterflyFeederSlot(container, 0, 80, 17));

        for(int i = 0; i < 3; ++i) {
            for(int j = 0; j < 9; ++j) {
                this.addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, i * 18 + 47));
            }
        }

        for(int i = 0; i < 9; ++i) {
            this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 105));
        }
    }

    /**
     * Called when a stack is shift-clicked.
     * @param player The player interacting with the menu.
     * @param slotIndex The slot that was shift-clicked.
     * @return The item stack that was moved.
     */
    @NotNull
    @Override
    public ItemStack quickMoveStack(@NotNull Player player,
                                    int slotIndex) {
        ItemStack result = ItemStack.EMPTY;
        Slot slot = this.slots.get(slotIndex);
        if (slot.hasItem()) {
            ItemStack item = slot.getItem();
            result = item.copy();

            if (slotIndex < this.feeder.getContainerSize()) {
                if (!this.moveItemStackTo(item, this.feeder.getContainerSize(), this.slots.size(), true)) {
                    return ItemStack.EMPTY;
                }
            } else if (!this.moveItemStackTo(item, 0, this.feeder.getContainerSize(), false)) {
                return ItemStack.EMPTY;
            }

            if (item.isEmpty()) {
                slot.setByPlayer(ItemStack.EMPTY);
            } else {
                slot.setChanged();
            }
        }

        return result;
    }

    /**
     * Called when the block entity is removed.
     * @param player The player interacting with the menu.
     */
    @Override
    public void removed(@NotNull Player player) {
        super.removed(player);
        this.feeder.stopOpen(player);
    }

    /**
     * Check the block entity is still valid.
     * @param player The player interacting with the menu.
     * @return TRUE if the block is still valid.
     */
    @Override
    public boolean stillValid(@NotNull Player player) {
        return this.feeder.stillValid(player);
    }
}

I’ve overrided the required methods here and added in some basic implementation. I don’t 100% understand what these methods can do, but the implementation here is the minimum that you need. The important part here is the constructor, where the slots are added to the menu.

As with everything else in Minecraft, we need to register our menu. There isn’t any menu registry yet, so I created a new one.

/**
 * Register for the menu types.
 */
public class MenuTypeRegistry {

    // An instance of a deferred registry we use to register menus.
    private final DeferredRegister<MenuType<?>> deferredRegister;

    // The butterfly feeder menu
    private RegistryObject<MenuType<ButterflyFeederMenu>> butterflyFeederMenu;

    /**
     * Construction
     * @param modEventBus The event bus to register with.
     */
    public MenuTypeRegistry(IEventBus modEventBus) {
        this.deferredRegister = DeferredRegister.create(ForgeRegistries.MENU_TYPES, ButterfliesMod.MOD_ID);
        this.deferredRegister.register(modEventBus);
    }

    /**
     * Register the menu types.
     */
    public void initialise() {
        this.butterflyFeederMenu = deferredRegister.register("butterfly_feeder",
                        () -> new MenuType<>(this::createButterflyFeederMenu, FeatureFlags.DEFAULT_FLAGS));
    }

    /**
     * Get the butterfly feeder menu.
     * @return The menu type.
     */
    public RegistryObject<MenuType<ButterflyFeederMenu>> getButterflyFeederMenu() {
        return butterflyFeederMenu;
    }

    /**
     * Helper method for creating butterfly feeder menu.
     * @param containerId The ID of the container.
     * @param playerInventory The player's inventory.
     * @return A new menu instance.
     */
    private ButterflyFeederMenu createButterflyFeederMenu(int containerId,
                                                          Inventory playerInventory) {
        return new ButterflyFeederMenu(this.butterflyFeederMenu.get(), containerId, playerInventory);
    }
}

To actually render the GUI, we of course need a texture. I created a simple texture that is basically just a modified version of the vanilla textures.

We can now create a screen that will display this texture when the GUI is displayed. Our screen is simple, just blitting the texture to the screen buffer.

/**
 * The GUI screen for a butterfly feeder.
 */
@OnlyIn(Dist.CLIENT)
public class ButterflyFeederScreen extends AbstractContainerScreen<ButterflyFeederMenu> {

    // The screen texture.
    private static final ResourceLocation TEXTURE =
            new ResourceLocation(ButterfliesMod.MOD_ID, "textures/gui/butterfly_feeder/butterfly_feeder.png");

    /**
     * Construction
     * @param butterflyFeederMenu The butterfly feeder menu.
     * @param inventory The player inventory.
     * @param title The menu title.
     */
    public ButterflyFeederScreen(ButterflyFeederMenu butterflyFeederMenu,
                                 Inventory inventory,
                                 Component title) {
        super(butterflyFeederMenu, inventory, title);
        this.imageHeight = 128;
        this.inventoryLabelY = this.imageHeight - 92;
    }

    /**
     * Render the tool tip.
     * @param guiGraphics The graphics object.
     * @param mouseX Mouse x-position.
     * @param mouseY Mouse y-position.
     * @param unknown Unknown.
     */
    public void render(@NotNull GuiGraphics guiGraphics,
                       int mouseX,
                       int mouseY,
                       float unknown) {
        super.render(guiGraphics, mouseX, mouseY, unknown);
        this.renderTooltip(guiGraphics, mouseX, mouseY);
    }

    /**
     * Render the background.
     * @param guiGraphics The graphics object.
     * @param unknown Unknown.
     * @param mouseX Mouse x-position.
     * @param mouseY Mouse y-position.
     */
    @Override
    protected void renderBg(@NotNull GuiGraphics guiGraphics,
                            float unknown,
                            int mouseX,
                            int mouseY) {
        int x = (this.width - this.imageWidth) / 2;
        int y = (this.height - this.imageHeight) / 2;
        guiGraphics.blit(TEXTURE, x, y, 0, 0, this.imageWidth, this.imageHeight);

    }
}

To link this screen to the menu we need to register the link during the FMLClientSetupEvent. I added a new event listener to the LifecycleEventListener class to handle this.

/**
 * Events fired during the overall life cycle of the mod.
 */
public class LifecycleEventListener {

    // Reference to the registries.
    private final ItemRegistry itemRegistry;
    private final MenuTypeRegistry menuTypeRegistry;

    /**
     * Construction
     * @param modEventBus The event bus to register with.
     */
    public LifecycleEventListener(IEventBus modEventBus,
                                  ItemRegistry itemRegistry,
                                  MenuTypeRegistry menuTypeRegistry) {
        modEventBus.register(this);
        modEventBus.addListener(this::clientSetup);
        modEventBus.addListener(this::commonSetup);

        this.itemRegistry = itemRegistry;
        this.menuTypeRegistry = menuTypeRegistry;
    }4

    /**
     * Register the screens with their respective menus.
     * @param event The client setup event.
     */
    private void clientSetup(FMLClientSetupEvent event) {
        event.enqueueWork(
                () -> MenuScreens.register(this.menuTypeRegistry.getButterflyFeederMenu().get(), ButterflyFeederScreen::new)
        );
    }
}

Now we can show a menu if we want, but there is still nothing in the game that uses this menu.

Block Entity


Block states are usually just a pointer to the block along with a few flags that show what they look like. They don’t have things like inventories, animations, and so on by default. This makes sense, since the game would grind to a halt if every block had every feature.

To create blocks with more interaction, such as chests, furnaces, brewing stands, and so on, Minecraft uses Block Entities. These are only created for certain blocks, and they allow for extra behaviours such as smelting, brewing potions, or just storing items.

The basic implementation for the butterfly feeder is essentially just a single slot inventory. So we can create our custom block entity using RandomizableContainerBlockEntity as a base, which is the same base used for chests.

/**
 * The block entity for a butterfly feeder.
 */
public class ButterflyFeederEntity extends RandomizableContainerBlockEntity {

    // Reference to the menu type registry.
    private final MenuTypeRegistry menuTypeRegistry;

    // The item(s) contained in the feeder.
    NonNullList<ItemStack> items;

    /**
     * Construction.
     * @param blockEntityType The block entity type.
     * @param blockPos The position of the block.
     * @param blockState The state of the block.
     */
    public ButterflyFeederEntity(MenuTypeRegistry menuTypeRegistry,
                                 BlockEntityType<?> blockEntityType,
                                 BlockPos blockPos,
                                 BlockState blockState) {
        super(blockEntityType, blockPos, blockState);
        this.menuTypeRegistry = menuTypeRegistry;
        this.items = NonNullList.withSize(1, ItemStack.EMPTY);
    }

    /**
     * The size of the container.
     * @return This is always 1.
     */
    @Override
    public int getContainerSize() {
        return 1;
    }

    /**
     * Get the default name for the feeder.
     * @return The relevant localisation string.
     */
    @NotNull
    @Override
    protected Component getDefaultName() {
        return Component.translatable("container.butterfly_feeder");
    }

    /**
     * Get the items in this container.
     * @return The item stack.
     */
    @NotNull
    @Override
    protected NonNullList<ItemStack> getItems() {
        return this.items;
    }

    /**
     * Set the items in the container.
     * @param items The items to set.
     */
    @Override
    protected void setItems(@NotNull NonNullList<ItemStack> items) {
        if (items.size() <= this.getContainerSize()) {
            this.items = items;
        }
    }
}

This implements the basic interface for the butterfly feeder. getContainerSize() tells the game that there is only a single slot in this entity’s inventory. We set a default name based on a localisation string. We use a list of item stacks even though there will only ever be on so we can fill out the interface properly.

To display the menu so that we can interact with this entity, I override the createMenu() method to return the menu I created above.

    /**
     * Create the container menu.
     * @param containerId The ID of this container.
     * @param inventory The player's inventory.
     * @return The menu to open.
     */
    @NotNull
    @Override
    protected AbstractContainerMenu createMenu(int containerId,
                                               @NotNull Inventory inventory) {
        return new ButterflyFeederMenu(this.menuTypeRegistry.getButterflyFeederMenu().get(),
                containerId, inventory, this);
    }

We still need to make sure that the data is saved, so that the items won’t disappear after Minecraft closes. To do this we need to add a couple more methods.

    /**
     * Load the inventory from a saved world.
     * @param compoundTag The compound tag.
     */
    @Override
    public void load(@NotNull CompoundTag compoundTag) {
        super.load(compoundTag);
        this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
        if (!this.tryLoadLootTable(compoundTag)) {
            ContainerHelper.loadAllItems(compoundTag, this.items);
        }
    }

    /**
     * Save the inventory.
     * @param compoundTag The compound tag.
     */
    protected void saveAdditional(@NotNull CompoundTag compoundTag) {
        super.saveAdditional(compoundTag);
        if (!this.trySaveLootTable(compoundTag)) {
            ContainerHelper.saveAllItems(compoundTag, this.items);
        }
    }

These methods will get called when the game saves or loads respectively. This solves the problem of saving, but we still have the problem of synchronisation. What happens on the server isn’t the same as what is happening on the client. To fix this we can add a few more methods to help with networking.

    /**
     * Create an update packet for this entity.
     * @return The update packet.
     */
    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    /**
     * Create an update tag that's synced with the client.
     * @return The update tag.
     */
    @NotNull
    @Override
    public CompoundTag getUpdateTag() {
        return this.saveWithoutMetadata();
    }

    @Override
    public void setChanged() {
        super.setChanged();

        if (this.level != null) {
            this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 2);
        }
    }

The getUpdatePacket() and the getUpdateTag() methods set the data the server needs to send to the client. The base class will call setChanged() every time the inventory changes, so in here we use sendBlockUpdated() to let the server know that new information needs to be sent to the client.

To allow us to use this in game we need to register the entity like we do with any other entity. But block entities need to use a different registry to normal entities, so we create a completely new class for this.

/**
 * Registers block entity types.
 */
public class BlockEntityTypeRegistry {

    // An instance of a deferred registry we use to register items.
    private final DeferredRegister<BlockEntityType<?>> deferredRegister;

    // The butterfly feeder entity.
    private RegistryObject<BlockEntityType<ButterflyFeederEntity>> butterflyFeeder;

    /**
     * Construction
     * @param modEventBus The event bus to register with.
     */
    public BlockEntityTypeRegistry(IEventBus modEventBus) {
        this.deferredRegister = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, ButterfliesMod.MOD_ID);
        this.deferredRegister.register(modEventBus);
    }

    /**
     * Register the block entities.
     * @param blockRegistry The block registry.
     */
    public void initialise(BlockRegistry blockRegistry) {

        //noinspection DataFlowIssue
        this.butterflyFeeder = this.deferredRegister.register("butterfly_feeder",
                () -> BlockEntityType.Builder.of(this::createButterflyFeeder,
                        blockRegistry.getButterflyFeeder().get()).build(null));

    }

    /**
     * Get the butterfly feeder.
     * @return The block entity type.
     */
    public RegistryObject<BlockEntityType<ButterflyFeederEntity>> getButterflyFeeder() {
        return butterflyFeeder;
    }

    /**
     * Create a butterfly feeder.
     * @param blockPos The position of the block.
     * @param blockState The block's state.
     * @return A new block entity.
     */
    private ButterflyFeederEntity createButterflyFeeder(BlockPos blockPos,
                                                        BlockState blockState) {
        return new ButterflyFeederEntity(menuTypeRegistry,
                butterflyFeeder.get(),
                blockPos,
                blockState);
    }
}

No we have an entity that can hold an inventory. But we cannot place it or interact with it. Nor does it actually feed any butterflies.

Block


To make the butterfly feeder block look good, I needed a model for it. I wanted something that looks like it could be in vanilla, so I opted for a metal bowl placed on top of something similar to a scaffolding. It would require iron ingots and bamboo to create so there will a small barrier to entry before players can create the block.

I used Blockbench to create a simple model for the block. Nothing too complicated, but should look good once it has texture.

I can use this to export a JSON file that will act as the model for the block. Doing this first is important, because I need to know how to set up the bounding box for the new block.

{
	"parent": "minecraft:block/block",
	"textures": {
		"0": "butterflies:block/butterfly_feeder/leg",
		"1": "butterflies:block/butterfly_feeder/base",
		"2": "butterflies:block/butterfly_feeder/bowl_rim1",
		"3": "butterflies:block/butterfly_feeder/bowl_rim2",
		"particle": "butterflies:block/butterfly_feeder/leg"
	},
	"elements": [
		{
			"from": [14, 0, 0],
			"to": [16, 12, 2],
			"rotation": {"angle": 0, "axis": "y", "origin": [14, 0, 0]},
			"faces": {
				"north": {"uv": [0, 0, 2, 12], "texture": "#0"},
				"east": {"uv": [2, 0, 4, 12], "texture": "#0"},
				"south": {"uv": [4, 0, 6, 12], "texture": "#0"},
				"west": {"uv": [6, 0, 8, 12], "texture": "#0"},
				"up": {"uv": [10, 2, 8, 0], "texture": "#0"},
				"down": {"uv": [10, 2, 8, 4], "texture": "#0"}
			}
		},
		{
			"from": [14, 0, 14],
			"to": [16, 12, 16],
			"rotation": {"angle": 0, "axis": "y", "origin": [14, 0, 14]},
			"faces": {
				"north": {"uv": [0, 0, 2, 12], "texture": "#0"},
				"east": {"uv": [2, 0, 4, 12], "texture": "#0"},
				"south": {"uv": [4, 0, 6, 12], "texture": "#0"},
				"west": {"uv": [6, 0, 8, 12], "texture": "#0"},
				"up": {"uv": [8, 0, 10, 2], "texture": "#0"},
				"down": {"uv": [8, 2, 10, 4], "texture": "#0"}
			}
		},
		{
			"from": [0, 0, 14],
			"to": [2, 12, 16],
			"rotation": {"angle": 0, "axis": "y", "origin": [0, 0, 14]},
			"faces": {
				"north": {"uv": [0, 0, 2, 12], "texture": "#0"},
				"east": {"uv": [2, 0, 4, 12], "texture": "#0"},
				"south": {"uv": [4, 0, 6, 12], "texture": "#0"},
				"west": {"uv": [6, 0, 8, 12], "texture": "#0"},
				"up": {"uv": [8, 0, 10, 2], "texture": "#0"},
				"down": {"uv": [8, 2, 10, 4], "texture": "#0"}
			}
		},
		{
			"from": [0, 12, 0],
			"to": [16, 14, 16],
			"faces": {
				"north": {"uv": [8, 0, 16, 1], "texture": "#1"},
				"east": {"uv": [8, 1, 16, 2], "texture": "#1"},
				"south": {"uv": [8, 2, 16, 3], "texture": "#1"},
				"west": {"uv": [8, 3, 16, 4], "texture": "#1"},
				"up": {"uv": [8, 8, 0, 0], "texture": "#1"},
				"down": {"uv": [8, 8, 0, 16], "texture": "#1"}
			}
		},
		{
			"from": [15, 14, 0],
			"to": [16, 16, 16],
			"rotation": {"angle": 0, "axis": "y", "origin": [15, 14, 0]},
			"faces": {
				"north": {"uv": [1, 2, 1.5, 3], "texture": "#2"},
				"east": {"uv": [0, 0, 8, 1], "texture": "#2"},
				"south": {"uv": [1.5, 2, 2, 3], "texture": "#2"},
				"west": {"uv": [0, 1, 8, 2], "texture": "#2"},
				"up": {"uv": [0.5, 10, 0, 2], "texture": "#2"},
				"down": {"uv": [1, 2, 0.5, 10], "texture": "#2"}
			}
		},
		{
			"from": [0, 14, 0],
			"to": [1, 16, 16],
			"rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 0]},
			"faces": {
				"north": {"uv": [1, 2, 1.5, 3], "texture": "#2"},
				"east": {"uv": [0, 0, 8, 1], "texture": "#2"},
				"south": {"uv": [1.5, 2, 2, 3], "texture": "#2"},
				"west": {"uv": [0, 1, 8, 2], "texture": "#2"},
				"up": {"uv": [0.5, 10, 0, 2], "texture": "#2"},
				"down": {"uv": [1, 2, 0.5, 10], "texture": "#2"}
			}
		},
		{
			"from": [1, 14, 15],
			"to": [15, 16, 16],
			"rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 15]},
			"faces": {
				"north": {"uv": [0, 0, 14, 2], "texture": "#3"},
				"east": {"uv": [0, 6, 1, 8], "texture": "#3"},
				"south": {"uv": [0, 2, 14, 4], "texture": "#3"},
				"west": {"uv": [1, 6, 2, 8], "texture": "#3"},
				"up": {"uv": [14, 5, 0, 4], "texture": "#3"},
				"down": {"uv": [14, 5, 0, 6], "texture": "#3"}
			}
		},
		{
			"from": [1, 14, 0],
			"to": [15, 16, 1],
			"rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 0]},
			"faces": {
				"north": {"uv": [0, 0, 14, 2], "texture": "#3"},
				"east": {"uv": [0, 6, 1, 8], "texture": "#3"},
				"south": {"uv": [0, 2, 14, 4], "texture": "#3"},
				"west": {"uv": [1, 6, 2, 8], "texture": "#3"},
				"up": {"uv": [14, 5, 0, 4], "texture": "#3"},
				"down": {"uv": [14, 5, 0, 6], "texture": "#3"}
			}
		},
		{
			"from": [0, 0, 0],
			"to": [2, 12, 2],
			"faces": {
				"north": {"uv": [0, 0, 2, 12], "texture": "#0"},
				"east": {"uv": [2, 0, 4, 12], "texture": "#0"},
				"south": {"uv": [4, 0, 6, 12], "texture": "#0"},
				"west": {"uv": [6, 0, 8, 12], "texture": "#0"},
				"up": {"uv": [8, 0, 10, 2], "texture": "#0"},
				"down": {"uv": [8, 2, 10, 4], "texture": "#0"}
			}
		}
	]
}

Now I can use these to create the ButterflyFeederBlock. This will allow the block to be placed in the world, and players will finally be able to interact with it.

/**
 * A butterfly feeder block.
 */
public class ButterflyFeederBlock extends BaseEntityBlock {

    //  The bottle's "model".
    private static final VoxelShape SHAPE = Shapes.or(
            Block.box(14.0,  0.0,  0.0, 16.0, 12.0,  2.0),
            Block.box(14.0,  0.0, 14.0, 16.0, 12.0, 16.0),
            Block.box( 0.0,  0.0, 14.0,  2.0, 12.0, 16.0),
            Block.box( 0.0, 12.0,  0.0, 16.0, 14.0, 16.0),
            Block.box(15.0, 14.0,  0.0, 16.0, 16.0, 16.0),
            Block.box( 0.0, 14.0,  0.0,  1.0, 16.0, 16.0),
            Block.box( 1.0, 14.0, 15.0, 15.0, 16.0, 16.0),
            Block.box( 1.0, 14.0,  0.0, 15.0, 16.0,  1.0),
            Block.box( 0.0,  0.0,  0.0,  2.0, 12.0,  2.0));

    // The registries.
    private final BlockEntityTypeRegistry blockEntityTypeRegistry;
    private final MenuTypeRegistry menuTypeRegistry;

    /**
     * Construction.
     * @param blockEntityTypeRegistry The block entity registry.
     * @param menuTypeRegistry The menu type registry.
     */
    public ButterflyFeederBlock(BlockEntityTypeRegistry blockEntityTypeRegistry,
                                MenuTypeRegistry menuTypeRegistry) {
        super(BlockBehaviour.Properties.of()
                .mapColor(MapColor.SAND)
                .isRedstoneConductor(BlockRegistry::never)
                .isSuffocating(BlockRegistry::never)
                .isValidSpawn(BlockRegistry::never)
                .isViewBlocking(BlockRegistry::never)
                .noOcclusion()
                .sound(SoundType.BAMBOO)
                .strength(0.3F));

        this.blockEntityTypeRegistry = blockEntityTypeRegistry;
        this.menuTypeRegistry = menuTypeRegistry;
    }

    /**
     * Get the shape of the block.
     * @param blockState The current block state.
     * @param blockGetter Access to the block.
     * @param position The block's position.
     * @param collisionContext The collision context we are fetching for.
     * @return The block's bounding box.
     */
    @NotNull
    @Override
    @SuppressWarnings("deprecation")
    public VoxelShape getShape(@NotNull BlockState blockState,
                               @NotNull BlockGetter blockGetter,
                               @NotNull BlockPos position,
                               @NotNull CollisionContext collisionContext) {
        return SHAPE;
    }

    /**
     * Tell this block to render as a normal block.
     * @param blockState The current block state.
     * @return Always MODEL.
     */
    @Override
    @NotNull
    public RenderShape getRenderShape(@NotNull BlockState blockState) {
        return RenderShape.MODEL;
    }
}

This creates a shape that uses a custom shape for collision and rendering. By specifying RenderShape.MODEL in getRenderShape‘s return value, the block will render as a model rather than the default cube.

To attach the block entity, we need to override newBlockEntity() to tell the game which entity to create when we place this block.

    /**
     * Create a block entity for this block.
     * @param blockPos The position of the block.
     * @param blockState The block's state.
     * @return A new block entity.
     */
    @Nullable
    @Override
    public BlockEntity newBlockEntity(@NotNull BlockPos blockPos,
                                      @NotNull BlockState blockState) {
        return new ButterflyFeederEntity(
                this.menuTypeRegistry, blockEntityTypeRegistry.getButterflyFeeder().get(),
                blockPos,
                blockState);
    }

To open the menu that was created above, I override the use() method.

    /**
     * Open the menu when the block is interacted with.
     * @param blockState The block's state.
     * @param level The current level.
     * @param blockPos The block's position.
     * @param player The player using the block.
     * @param interactionHand The hand interacting with the block.
     * @param blockHitResult The result of the collision detection.
     * @return The result of the interaction (usually consumed).
     */
    @NotNull
    @Override
    @SuppressWarnings("deprecation")
    public InteractionResult use(@NotNull BlockState blockState,
                                 Level level,
                                 @NotNull BlockPos blockPos,
                                 @NotNull Player player,
                                 @NotNull InteractionHand interactionHand,
                                 @NotNull BlockHitResult blockHitResult) {
        if (level.isClientSide) {
            return InteractionResult.SUCCESS;
        } else {
            BlockEntity blockEntity = level.getBlockEntity(blockPos);
            if (blockEntity instanceof ButterflyFeederEntity feederEntity) {
                player.openMenu(feederEntity);
            }

            return InteractionResult.CONSUME;
        }
    }

Now, if a player right-clicks on the block, the new menu will open.

One thing we still need to handle is to make sure the block drops everything when it breaks. Normally you handle this using a loot table like this.

{
  "pools": [
    {
      "bonus_rolls": 0.0,
      "conditions": [
        {
          "condition": "minecraft:survives_explosion"
        }
      ],
      "entries": [
        {
          "name": "butterflies:butterfly_feeder",
          "type": "minecraft:item"
        }
      ],
      "rolls": 1
    }
  ],
  "random_sequence": "butterflies:blocks/butterfly_feeder",
  "type": "minecraft:block"
}

This will drop the block, but any items inside will disappear from the world. To fix this we can override the block’s onRemove() method.

    /**
     * Drop the container's items when it is destroyed.
     * @param blockState The current block state.
     * @param level The current level.
     * @param blockPos The block's position.
     * @param newBlockState The new block state.
     * @param unknown Unknown flag.
     */
    @Override
    @SuppressWarnings("deprecation")
    public void onRemove(BlockState blockState,
                         @NotNull Level level,
                         @NotNull BlockPos blockPos,
                         BlockState newBlockState,
                         boolean unknown) {
        if (!blockState.is(newBlockState.getBlock())) {
            BlockEntity blockEntity = level.getBlockEntity(blockPos);
            if (blockEntity instanceof Container) {
                Containers.dropContents(level, blockPos, (Container)blockEntity);
                level.updateNeighbourForOutputSignal(blockPos, this);
            }

            super.onRemove(blockState, level, blockPos, newBlockState, unknown);
        }
    }

The code for the block is complete now, all we need to do is to register the block. This is done in the BlockRegistry.

        this.butterflyFeeder = deferredRegister.register( "butterfly_feeder",
                () -> new ButterflyFeederBlock(blockEntityTypeRegistry, menuTypeRegistry));

To register the item we just use the vanilla BlockItem class.

        this.butterflyFeeder =deferredRegister.register("butterfly_feeder",
                        () -> new BlockItem(blockRegistry.getButterflyFeeder().get(), new Item.Properties()));

I worked on some simple textures to make the block look nice, and I like the result so far.

The last touch is to add a recipe and a way of unlocking it to the game, and the block is almost complete.

Visual Feedback


One thing I wanted to was to provide visual feedback to show how much food was in the feeder. In order to do this I created a custom entity renderer for the block entity. It’s render() method renders up to 8 models based on the number of items in the container. I programmed it to rotate and translate each model a set amount, so it would slowly spread out and fill the container as more items were placed into it. I also used the block’s position as “noise” to set where the rotation starts. This makes individual blocks look different even if they have the same number of items in them.

/**
 * Renders the items stored in a butterfly feeder.
 */
public class ButterflyFeederEntityRenderer implements BlockEntityRenderer<ButterflyFeederEntity> {

    /**
     * Construction
     * @param ignoredContext This is ignored.
     */
    public ButterflyFeederEntityRenderer(BlockEntityRendererProvider.Context ignoredContext) {
    }

    /**
     * Render items based on what is in the feeder and how many items are in
     * the feeder.
     * @param butterflyFeederEntity The butterfly feeder entity.
     * @param v Unknown.
     * @param poseStack The matrix stack.
     * @param multiBufferSource The render buffer.
     * @param unknown1 Unknown.
     * @param unknown2 Unknown.
     */
    @Override
    public void render(@NotNull ButterflyFeederEntity butterflyFeederEntity,
                       float v,
                       @NotNull PoseStack poseStack,
                       @NotNull MultiBufferSource multiBufferSource,
                       int unknown1,
                       int unknown2) {

        if (!butterflyFeederEntity.isEmpty()) {
            ItemStack itemStack = butterflyFeederEntity.getItem(0);
            int count = itemStack.getCount();

            // We'll render one for every 8 items, so we don't slow things down
            // too much.
            for (int i = 0, j = 0; i < count; i += 8, j = (j + 1) % 4 ) {
                double xMod = i * 0.005;
                double yMod = i * 0.005;

                if (j >= 2) {
                    xMod *= -1;
                }

                if (j % 2 != 0) {
                    yMod *= -1;
                }

                // Use a "random" starting rotation based on the block
                // position.
                BlockPos blockPos = butterflyFeederEntity.getBlockPos();
                float yRot = (float)blockPos.getCenter().lengthSqr();
                yRot += i * 3.0f;

                renderItem(
                        butterflyFeederEntity,
                        poseStack,
                        multiBufferSource,
                        unknown1,
                        unknown2,
                        0.5 + xMod,
                        0.5 + yMod,
                        yRot);
            }
        }
    }

    /**
     * Render an individual item.
     * @param butterflyFeederEntity The butterfly feeder entity.
     * @param poseStack The matrix stack.
     * @param multiBufferSource The render buffer.
     * @param i Unknown.
     * @param i1 Unknown.
     * @param x The x-offset of the item.
     * @param z The z-offset of the item.
     * @param yRot The y-rotation of the item.
     */
    private void renderItem(@NotNull ButterflyFeederEntity butterflyFeederEntity,
                            @NotNull PoseStack poseStack,
                            @NotNull MultiBufferSource multiBufferSource,
                            int i,
                            int i1,
                            double x,
                            double z,
                            float yRot) {
        ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();

        poseStack.pushPose();
        poseStack.translate(x, 0.9, z);
        poseStack.mulPose(Axis.YP.rotationDegrees(yRot));

        itemRenderer.renderStatic(
                butterflyFeederEntity.getItem(0),
                ItemDisplayContext.GROUND,
                i,
                i1,
                poseStack,
                multiBufferSource,
                butterflyFeederEntity.getLevel(),
                0);

        poseStack.popPose();
    }
}

To hook this up to the block entity we can do this in our RegisterRenderers event listener.

    private void onRegisterRenderers(final EntityRenderersEvent.RegisterRenderers event)
    {
        event.registerBlockEntityRenderer(blockEntityTypeRegistry.getButterflyFeeder().get(), ButterflyFeederEntityRenderer::new);
    }

Now we can get visual feedback on each butterfly feeder without having to open the menu!

Feeding the Butterflies


You may have noticed something missing from this entire implementation. There’s still no code to actually feed the butterflies. The butterfly feeders look nice and you can store food in them, but they don’t actually do anything yet.

To support butterflies actually using these blocks, I modified the ButterflyPollinateFlowerGoal. To start with, they can now use these blocks as targets as long as they have zero eggs.

    @Override
    protected boolean isValidTarget(@NotNull LevelReader levelReader,
                                    @NotNull BlockPos blockPos) {
        if (! levelReader.getBlockState(blockPos.above()).isAir()) {
            return false;
        }

        // Butterflies will look for feeders.
        if (butterfly.getNumEggs() == 0 &&
                levelReader.getBlockEntity(blockPos) instanceof ButterflyFeederEntity feeder) {
            if (feeder.getItem(0).is(preferredFlowerItem)) {
                return true;
            }
        }

        BlockState blockState = levelReader.getBlockState(blockPos);

        // If this is the butterfly's preferred flower it is always valid.
        if (blockState.is(this.preferredFlowerBlock)) {
            return true;
        }

        if (blockState.is(BlockTags.SMALL_FLOWERS)) {

            // Butterflies will only fly to other flowers 50% of the time.
            return (this.random.nextInt() % 2 == 0);
        }

        return false;
    }

This will make them fly toward the block, but the actual feeding occurs when they reach the block itself.

    @Override
    public void tick() {
        super.tick();

        if (this.isReachedTarget()) {

            // Don't stay in the landed state for too long.
            this.tryTicks -= 11;
            Vec3 deltaMovement = this.butterfly.getDeltaMovement();
            this.butterfly.setDeltaMovement(0.0, deltaMovement.y, 0.0);

            if (!attemptedToPollinate) {
                attemptedToPollinate = true;

                if (butterfly.level().getBlockEntity(blockPos) instanceof ButterflyFeederEntity feeder) {
                    if (feeder.getItem(0).is(preferredFlowerItem)) {
                        butterfly.setNumEggs(1);
                        feeder.removeItem(0, 1);
                    }
                } else {
                    if (this.random.nextInt() % 5 == 0) {
                        BlockPos spawnPos = findNearestFlowerSpot();
                        if (spawnPos != null) {
                            BlockState blockState = this.mob.level().getBlockState(this.blockPos);
                            Block budBlock = getFlowerBud(blockState.getBlock());
                            if (budBlock != null) {
                                this.mob.level().setBlockAndUpdate(spawnPos, budBlock.defaultBlockState());
                            }
                        }
                    }
                }
            }
        }
    }

Now butterflies that are looking to pollinate flowers may instead feed off one of the items in a butterfly feeder. The feeder will need the butterfly’s preferred food in order to work, so players will have to use mutliple feeders if they want to breed multiple species.

Job Block


The next goal I have now is to create a custom villager that will use the butterfly feeder as a job block. This villager will have several butterfly-related trades and will be another way for players to obtain butterflies of various species.

    One thought on “A New Way to Feed Butterflies

    Comments are closed.