Very Intelligent Butterflies

Butterflies aren’t very smart in real life. But they are smarter than the butterflies currently implemented in the Butterfly Mod. So I’ve started implementing new goals for the butterfly’s AI to make them behave just a bit smarter than they have been so far.

Last week we modified butterflies so that they used Minecraft’s goal AI and pathfinding for movement. With this change, we can now start to introduce new goals that can modify the behaviour of a butterfly entity. To start with, we have added a new behaviour that allows butterflies to land on leaves and lay an egg.

Laying Eggs with Intelligence


Butterflies lay eggs randomly as they get close to leaves. I wanted to change things so that butterflies would lay eggs with a purpose. Instead of being a random event that would occur as they flew around, butterflies that were fertile will actively seek out a leaf block and lay their eggs. The natural starting point for this was to implement a new goal.

/**
 * Goal that allows butterflies to lay eggs on leaves.
 */
public class ButterflyLayEggGoal extends MoveToBlockGoal {

    //  The butterfly using this goal.
    private final Butterfly butterfly;

    /**
     * Construction
     * @param mob The instance of the butterfly.
     * @param speedModifier The speed modifier applied when this goal is in progress.
     * @param searchRange The range to search for blocks.
     * @param verticalSearchRange The vertical range to search for blocks.
     */
    public ButterflyLayEggGoal(Butterfly mob,
                                    double speedModifier,
                                    int searchRange,
                                    int verticalSearchRange) {
        super(mob, speedModifier, searchRange, verticalSearchRange);
        this.butterfly = mob;
    }

    /**
     * We can only use this goal if the butterfly has an egg to lay.
     * @return TRUE if we can use this goal.
     */
    @Override
    public boolean canUse() {
        return butterfly.getIsFertile() && super.canUse();
    }

    /**
     * Start using the goal - ensure the butterfly is not landed.
     */
    @Override
    public void start() {
        this.butterfly.setLanded(false);
        super.start();
    }

    /**
     * Update the butterfly after it has landed.
     */
    @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.setLanded(true);
            this.butterfly.setDeltaMovement(0.0, deltaMovement.y, 0.0);

            if (this.butterfly.getIsFertile()) {

                // Don't lay an egg if there are too many butterflies in the area already.
                List<Butterfly> numButterflies = this.butterfly.level().getNearbyEntities(
                        Butterfly.class,
                        TargetingConditions.forNonCombat(),
                        butterfly,
                        this.butterfly.getBoundingBox().inflate(32.0D));

                int maxDensity = ButterfliesConfig.maxDensity.get();
                if (maxDensity == 0 || numButterflies.size() <= maxDensity) {

                    // Attempt to lay an egg.
                    Direction direction = switch (this.butterfly.getRandom().nextInt(6)) {
                        default -> Direction.UP;
                        case 1 -> Direction.DOWN;
                        case 2 -> Direction.NORTH;
                        case 3 -> Direction.EAST;
                        case 4 -> Direction.SOUTH;
                        case 5 -> Direction.WEST;
                    };

                    if (this.butterfly.level().getBlockState(this.blockPos.relative(direction)).isAir()) {
                        ResourceLocation eggEntity = ButterflyData.indexToButterflyEggEntity(this.butterfly.getButterflyIndex());
                        ButterflyEgg.spawn((ServerLevel) this.butterfly.level(), eggEntity, this.blockPos, direction);
                        this.butterfly.setIsFertile(false);
                        this.butterfly.useEgg();
                    }
                }
            }
        } else {
            //  Give up on pathfinding quicker.
            this.tryTicks += 11;
        }
    }

    /**
     * Tells the base goal which blocks are valid targets.
     * @param levelReader Gives access to the level.
     * @param blockPos The block position to check.
     * @return TRUE if the block is a valid target.
     */
    @Override
    protected boolean isValidTarget(@NotNull LevelReader levelReader,
                                    @NotNull BlockPos blockPos) {

        if (!levelReader.isEmptyBlock(blockPos.above())) {
            return false;
        }

        if (levelReader.getBlockState(blockPos).is(BlockTags.LEAVES)) {
            return blockPos.getY() < this.butterfly.getBlockY();
        }

        return false;
    }
}

We inherit from Minecraft’s MoveToBlockGoal which does most of the work for us in terms of pathfinding. All we need to do is tell the goal what is a valid target. If you look at the isValidTarget() method you will see that we need a leaf block with some air above it.

In order for the butterfly to use this goal we check if the butterfly is fertile in canUse(). This method is made public in the Butterfly class, and now also checks we actually have eggs we can lay.

    /**
     * Check if the butterfly can lay an egg.
     * @return TRUE if the butterfly is fertile.
     */
    public boolean getIsFertile() {
        return getNumEggs() > 0 && entityData.get(DATA_IS_FERTILE);
    }

You should recognise the code used in the tick() method. This code is taken from customServerAiStep() in the Butterfly class, that was originally written so butterflies would lay eggs. With some slight modifications, this will allow the butterfly to lay an egg on the leaf block it lands on. The code has been removed from the Butterfly class, and some methods have been made public so that we can rely on just this code now.

One thing to note about the tick is how we increase/decrease tryTicks. By default an entity can stay at a block for up to two minutes, but butterflies tend not to stay in the same place for too long, so this helps to reduce the time it will stay on a block.

You will also notice calls to setLanded() in the tick method, as well as in start(). This is a new property on the Butterfly that tells us whether or not the butterfly has landed. It is set by the server and synchronised to clients, as well as being stored in the save data so butterflies are in the same state when we save/reload the game.

Now we have this goal set up we can add it to the list of goals in our Butterfly class. This will make the butterfly seek out and lay eggs on leaves, as long as they are fertile. With it written as a goal like this, the butterfly’s behaviour will appear more natural.

    /**
     * Register the goals for the entity.
     */
    @Override
    protected void registerGoals() {
        super.registerGoals();

        this.goalSelector.addGoal(2, new ButterflyLayEggGoal(this, 0.8d, 8, 8));
        this.goalSelector.addGoal(8, new ButterflyWanderGoal(this));
    }

Pollinating Flowers (Almost)


There was a suggestion from GreenForzeGaming to allow butterflies to pollinate and breed flowers. I like this idea, and am planning to implement it, but I still haven’t quite figured out how it’s going to work. I really like their suggestion for butterflies having preferred flowers, and it’s something I plan to include in the next version of the mod.

For now I have a placeholder goal implemented that will be updated in the future.

/**
 * Goal that enables butterflies to pollinate flowers.
 */
public class ButterflyPollinateFlowerGoal extends MoveToBlockGoal {

    //  The butterfly using this goal.
    private final Butterfly butterfly;

    /**
     * Construction
     * @param mob The instance of the butterfly.
     * @param speedModifier The speed modifier applied when this goal is in progress.
     * @param searchRange The range to search for blocks.
     * @param verticalSearchRange The vertical range to search for blocks.
     */
    public ButterflyPollinateFlowerGoal(Butterfly mob,
                                        double speedModifier,
                                        int searchRange,
                                        int verticalSearchRange) {
        super(mob, speedModifier, searchRange, verticalSearchRange);
        this.butterfly = mob;
    }

    /**
     * Start using the goal - ensure the butterfly is not landed.
     */
    @Override
    public void start() {
        this.butterfly.setLanded(false);
        super.start();
    }

    /**
     * Update the butterfly after it has landed.
     */
    @Override
    public void tick() {
        super.tick();

        if (this.isReachedTarget()) {
            // Don't stay in the landed state for too long.
            this.tryTicks -= 11;
            this.butterfly.setDeltaMovement(0.0, 0.0, 0.0);
        } else {
            //  Give up on pathfinding quicker.
            this.tryTicks += 11;
        }
    }

    /**
     * Tells the base goal which blocks are valid targets.
     * @param levelReader Gives access to the level.
     * @param blockPos The block position to check.
     * @return TRUE if the block is a valid target.
     */
    @Override
    protected boolean isValidTarget(@NotNull LevelReader levelReader,
                                    @NotNull BlockPos blockPos) {
        return levelReader.getBlockState(blockPos).is(BlockTags.FLOWERS);
    }
}

You will see a lot of similarities with the previous goal, minus the code for laying eggs. In this instance, the butterfly will seek out flowers, and will hover by them for a moment before moving on. The animation is there, but the gameplay isn’t yet.

As before, we can add the goal to the Butterfly class.

    /**
     * Register the goals for the entity.
     */
    @Override
    protected void registerGoals() {
        super.registerGoals();

        this.goalSelector.addGoal(2, new ButterflyLayEggGoal(this, 0.8d, 8, 8));
        this.goalSelector.addGoal(4, new ButterflyPollinateFlowerGoal(this, 0.8d, 8, 8));
        this.goalSelector.addGoal(8, new ButterflyWanderGoal(this));
    }

Now we have butterflies that behave with a little bit more intelligence.

“Fixing” the Movement


During my testing I had an issue with the way butterflies moved. Whenever they touched the ground they would fly off in a random direction at the speed of sound. I spent a long time trying to figure out what the problem was. I managed to narrow it down to collision detection, but was still at a loss on how to fix it.

In the end I wrote a very hacky fix for this issue. Basically, it stops the butterfly from moving too fast. It isn’t perfect, but it seems to work.

    /**
     * Hacky fix to stop butterflies teleporting.
     * TODO: We need a better fix than this.
     * @param x The x-position.
     * @param y The y-position.
     * @param z The z-position.
     */
    @Override
    public void setPos(double x, double y, double z) {
        Vec3 delta = new Vec3(x, y, z).subtract(this.position());
        if (delta.lengthSqr() <= 2 ||
            this.position().lengthSqr() == 0) {
            super.setPos(x, y, z);
        }
    }

I really don’t like this “fix” however. It’s a hack that covers up the bug, rather than an actual fix. I may look at it again in the future, and hopefully I’ll be able to fix it properly.

Future Goals


Butterflies are much smarter now, but they can be even more smart than me is. I plan to improve upon this with a few more goals for the butterfly.

Fleeing

Butterflies in real life can be startled easily, and I want this to happen in the mod as well. This goal will activate if a butterfly detects another entity moving nearby, and will cause them to fly away from the entity that startled it.

Mating

Butterflies mate automatically by being near other butterflies at the moment. By creating a goal for them, butterflies can actively seek out other members of the same species and mate with purpose. It will make them behave in a more natural way, and allows us to remove even more code from the Butterfly class.

Pollination

I already talked about this above, but I want to add the ability for butterflies to pollinate flowers and allow new flowers to grow. This way, flower farms will be possible using butterflies. Of course, I don’t want them to overrun worlds with flowers as I have done before with butterflies, so I need to figure out exactly how to make this work.

One thought on “Very Intelligent Butterflies

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.