Butterflies Float Like a Bee

Butterflies aren’t smart. At least in the Butterfly mod they aren’t. Minecraft has an AI system that I haven’t taken advantage of so far. However, I’m planning on introducing some new behaviour to the butterflies, so it’s time to evolve.

Goals


The AI system in Minecraft works by defining goals for each entity. The entity will have a prioritised list of goals, and will attempt to run each one when it can. In order to take advantage of the AI system, we first need to convert our butterfly’s movement code to a goal. We remove the movement code from customServerAiStep and replace it with a goal.

/**
 * Wander goal to determine the position a butterfly will move to.
 */
public class ButterflyWanderGoal extends Goal {
    private final Butterfly butterfly;

    /**
     * Construction - set this to a movement goal.
     * @param butterfly The entity this goal belongs to.
     */
    public ButterflyWanderGoal(Butterfly butterfly) {
        this.setFlags(EnumSet.of(Flag.MOVE));
        this.butterfly = butterfly;
    }

    /**
     * Returns TRUE if we can keep using this goal.
     * @return Whether we can continue to use the goal.
     */
    @Override
    public boolean canContinueToUse() {
        return this.butterfly.getNavigation().isInProgress();
    }


    /**
     * Returns TRUE if we can use this goal.
     * @return Whether we can use the goal.
     */
    @Override
    public boolean canUse() {
        return this.butterfly.getNavigation().isDone();
    }

    /**
     * Start using the goal - sets a target for our navigation.
     */
    @Override
    public void start() {
        Vec3 vec3 = this.findPos();
        this.butterfly.getNavigation().moveTo(this.butterfly.getNavigation().createPath(BlockPos.containing(vec3), 1), 1.0);

    }

    /**
     * Gets a random position for our butterfly to fly to.
     * @return A random position.
     */
    @NotNull
    private Vec3 findPos() {
        Vec3 position = butterfly.position();
        return position.add(butterfly.getRandom().nextInt(8) - 4,
                            butterfly.getRandom().nextInt(6) - 2,
                            butterfly.getRandom().nextInt(8) - 4);
    }
}

In our constructor we set the MOVE flag to indicate that this is a movement goal. Their are four different types of goals that may be active at the same time: MOVE, LOOK, JUMP, and TARGET. The latter is used with other goals to specify a target for the entity (usually attack based goals).

canUse() tells us when the butterfly can use this goal. In this case the goal can always be used as long as the current navigation has finished. canContinueToUse() lets us know if we can keep using the goal after it has started, which we can for this goal as long as navigation is still in progress.

In start() we find a random position using the same algorithm we deleted from the entity’s code, and then we start navigating to that position using Minecraft’s navigation system.

With this goal defined, we can override a method in the Butterfly class to add this new goal to the entity.

    /**
     * Register the goals for the entity.
     */
    @Override
    protected void registerGoals() {
        super.registerGoals();
        this.goalSelector.addGoal(8, new ButterflyWanderGoal(this));
    }

We set the priority of the goal to 8 here. This isn’t really important now as there is only one goal, but it gives us room for goals of a lower priority if we need them.

Now that we have a movement goal, we need to take advantage of Minecraft’s pathfinding system to ensure butterflies can still fly.

Navigation


This goal works, but now we have a problem. By default Minecraft mobs walk on the ground. In order to get our butterflies to fly we need to change the way they handle navigation. To do this, we need to add a few things to the Butterfly class.

The first thing we add are some values for the butterfly’s speed. These are used by the navigation system to determine how fast the butterfly moves.

    /**
     * Supplies attributes for the butterfly, in this case just 3 points of
     * maximum health (1.5 hearts).
     * @return The butterfly attribute supplier.
     */
    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes()
                .add(Attributes.MAX_HEALTH, 3d)
                .add(Attributes.FLYING_SPEED, BUTTERFLY_SPEED)
                .add(Attributes.MOVEMENT_SPEED, BUTTERFLY_SPEED * 05.d);
    }

Our butterflies still move on the ground, however, so we to change their move controller from the default to a flying move controller. We can do this by adding a single line to the constructor.

        this.moveControl = new FlyingMoveControl(this, 20, true);

The second parameter sets the turn speed for the entity, and the last parameter tells the controller to ignore gravity if it is set to true.

We’re almost there, but there is still one more thing to do. The default navigator for entities is designed to only path-find along the ground. We need a navigator that can path-find through the air. Minecraft has a navigator for flying entities already, and we can use it by overriding createNavigation().

    /**
     * Create a flying navigator for the butterfly.
     * @param level The current level.
     * @return The flying navigation.
     */
    @Override
    @NotNull
    protected PathNavigation createNavigation(@NotNull Level level) {
        FlyingPathNavigation navigation = new FlyingPathNavigation(this, level) {
            public boolean isStableDestination(@NotNull BlockPos blockPos) {
                return this.level.getBlockState(blockPos).isAir();
            }
        };

        if (speed == ButterflyData.Speed.FAST) {

            navigation.setSpeedModifier(1.2);
        }

        navigation.setCanOpenDoors(false);
        navigation.setCanFloat(false);
        navigation.setCanPassDoors(true);
        return navigation;
    }

We override isStableDestination() here since any air block is a valid destination for a butterfly. We also apply a speed modifier if this is a FAST butterfly.

Now our butterflies can fly around as they did before, but they take advantage of the AI and navigation systems already in place rather than reinventing the wheel.

Unfortunately, these changes have left us with a small bug.

Disorientation


Way back when I created the models for the butterflies, I gave no thought as to what their default orientation should be. They worked thanks to custom code that I wrote to control their movement and orientation.

Now that we are using Minecraft’s code for movement and orientation, this has highlighted a bug. The models are facing the wrong way, so they move sideways like flying crabs.

The proper fix would be to recreate the models using the correct orientation. Unfortunately I don’t have access to the original Blockbench models, and I was worried that by doing it manually I would introduce more subtle bugs.

Thankfully, there is a quicker and easier fix. I just update the renderer to rotate the butterfly by 90 degrees, and butterflies now look in the correct direction as they fly.

    /**
     * Override to fix a bug with the model's orientation.
     * @param entity The butterfly entity.
     * @param p_115456_ Unknown.
     * @param p_115457_ Unknown.
     * @param poseStack The pose stack.
     * @param multiBufferSource The render buffer (I think...)
     * @param p_115460_ Unknown.
     */
    @Override
    public void render(@NotNull Butterfly entity,
                       float p_115456_,
                       float p_115457_,
                       @NotNull PoseStack poseStack,
                       @NotNull MultiBufferSource multiBufferSource,
                       int p_115460_) {

        // When the models were initially created no thought was given as to
        // what orientation they needed to be in. Rotating them here allows
        // them to use Minecraft's pathfinding systems without having to redo
        // the model from scratch.
        poseStack.mulPose(Axis.YP.rotationDegrees(-90.f));

        super.render(entity, p_115456_, p_115457_, poseStack, multiBufferSource, p_115460_);
    }

I’ve left a descriptive comment for future reference. This is in case this causes some other issues I haven’t foreseen, to remind myself of what the more robust fix for this bug will be.

New Goals


Now that we have the butterflies set up to use the goal-oriented AI, we can start to add new behaviours to the butterflies. My next goal is to get the butterflies to land on certain objects, and also to flee from nearby entities. These will be new Goals implemented using this system, and will lead to much smarter 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.