The Butterfly’s Final Goals

I’m getting close to finishing version 4 of Bok’s Butterflies. I just need to cross some Is and dot some Ts. A couple of new goals and a bugfix or two, then port the mod to all supported versions, and it’s ready for release.

Mating


Currently, butterfly mating is complete luck of the draw. If a butterfly happens to be near another of the same species, then it will become fertile and can lay an egg.

I wanted to change this so that butterflies would mate with an intention, and maybe do a bit of a dance like they would in real life. To do this I created a new goal, this one based off of the vanilla MoveTowardsTargetGoal.

/**
 * A goal for butterflies that want to mate.
 */
public class ButterflyMatingGoal extends MoveTowardsTargetGoal {

    // The squared distance at which butterflies can mate.
    private static final double MATING_DISTANCE_SQUARED = 2.0 * 2.0;

    // The butterfly that owns the goal.
    private final Butterfly butterfly;

    /**
     * Construction
     * @param pathfinderMob The mob that owns this goal.
     * @param speedModifier The speed modifier applied when using this goal.
     * @param within The distance within which to search for a target.
     */
    public ButterflyMatingGoal(Butterfly pathfinderMob, double speedModifier, float within) {
        super(pathfinderMob, speedModifier, within);
        this.butterfly = pathfinderMob;
    }

    /**
     * Can only use the goal if butterflies have eggs that need fertilising.
     * @return TRUE if the butterfly can mate.
     */
    @Override
    public boolean canUse() {
        return this.butterfly.getNumEggs() > 0 &&
               !this.butterfly.getIsFertile() &&
               super.canUse();
    }

    /**
     * Do the actual mating if the butterflies get close enough to each other.
     */
    @Override
    public void tick() {
        super.tick();

        if (!this.butterfly.getIsFertile()) {
            LivingEntity target = this.butterfly.getTarget();
            if (target != null && this.butterfly.distanceToSqr(target) < MATING_DISTANCE_SQUARED) {
                this.butterfly.setIsFertile(true);
                this.butterfly.setInLove(null);
            }
        }
    }
}

In the canUse() method we check if the butterfly has eggs that need to be fertilised. In our tick() method, we actually set them to fertilised if they get close enough to their potential partner. The base class does the rest of the work for us, telling the butterfly to move towards its desired mate.

Now that we have a goal, we can add it to the butterfly. Since mating and laying eggs are mutually exclusive goals, we can set them to have the same priority.

        this.goalSelector.addGoal(2, new ButterflyLayEggGoal(this, 0.8, 8, 8));
        this.goalSelector.addGoal(2, new ButterflyMatingGoal(this, 1.1, 8));

This still doesn’t get butterflies mating however. They still need a way to choose a mate. Minecraft has a separate goal mechanism for selecting targets. This is usually used for selecting targets to attack, but since butterflies don’t attack, we can use it for selecting a mate instead.

        // Butterflies use targets to select mates.
        this.targetSelector.addGoal(0, new NearestAttackableTargetGoal<>(this, Butterfly.class, true, (target) -> {
                if (target instanceof Butterfly butterfly) {
                    return butterfly.getButterflyIndex() == this.getButterflyIndex() &&
                           butterfly.getNumEggs() > 0 &&
                           !butterfly.getIsFertile();
                }

                return false;
            }));

For these goals, instead of adding them to the goalSelector, we can add them to the targetSelector. This tells Minecraft that this goal will be used for selecting a target. For our purposes we can use NearestAttackableTargetGoal rather than write our own, since it does everything we need it to by default.

The goal is set to choose only butterflies, but we also use a predicate to ensure that the other butterfly is of the same species (i.e. uses the same butterflyIndex) and is also able to mate. We just use a lambda here to check the conditions we need.

Fleeing


One other behaviour I wanted to add to butterflies is to have them flee when other entities get near. This one is easy to implement, as we can just use Minecraft’s AvoidEntityGoal.

        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, LivingEntity.class, 3, 0.8, 1.33, (x) -> !(x instanceof Butterfly)));

I set it to avoid all living entities, but that also includes other butterflies. Thankfully we can use a predicate here as well, which will only return true if the entity isn’t a butterfly.

Moving


These last few weeks as I’ve been modifying butterfly behaviour I keep running into this problem that I haven’t been able to resolve. They will try and fly through the ground and end up flying off in all different directions. I minimised this problem with a hacky fix, but it wasn’t enough to make the issue go away. It turns out I can fix it by adding some code I removed a while ago.

    /**
     * Reduce vertical movement to minimise collision errors.
     */
    @Override
    public void tick() {
        super.tick();

        //  Reduce the vertical movement to keep the butterfly close to the
        //  same height.
        this.setDeltaMovement(this.getDeltaMovement().multiply(1.0d, 0.6d, 1.0d));
    }

I originally based butterfly movement off the bat’s code, and this was one of the lines I copied. When I was looking at the movement again, I didn’t understand why this was here so I removed the code. Now I know why – I wonder if bats had the same problem when they were first added into Minecraft and this was their fix.

Whatever the reason, running the game with this code in place keeps the butterflies from colliding with the ground (mostly…), and makes their movement look like it was before I switched over to using goals and navigation to control their movement.

Pollination Release


I’m so close to a new release now. I just need to port the changes I’ve made to the other versions of Minecraft and it will be ready.

One more week…?