Better Butterfly Landings

One feature that’s bugged me (pun intended) for a while in Bok’s Banging Butterflies is how butterflies only ever land on the tops of blocks. In real life, butterflies and moths land wherever they please. They’ll land on walls or ceilings, completely ignoring gravity like tiny winged rebels. So this week, I decided it was time to bring a little more realism to the mod and let butterflies land anywhere.

Weightless


The Butterfly landing code isn’t great. When they land, rather than setting a position, I re-enable gravity on them so they fall to the block. This works fine, but only in one direction: down.

To allow butterflies to land in any direction in Bok’s Banging Butterflies, the first thing I had to was disable gravity for them completely. This was handled by a single line in the Butterfly class right after it sets whether or not a butterfly is Landed. All I needed to do was remove this line:

        this.setNoGravity(!landed);

With that gone, gravity is no longer a factor. But this means I now have to manually set both the position and orientation of a landed butterfly.

Positioning


To track the direction a butterfly has landed, I added a new data property to the Butterfly class to store the direction of the landing block, similar to how it works with caterpillars and other entities in the mod.

    protected static final EntityDataAccessor<Direction> DATA_DIRECTION =
            SynchedEntityData.defineId(Butterfly.class, EntityDataSerializers.DIRECTION);

Next, I updated the setLanded() method so that when a butterfly lands, it places itself just right depending on the direction of the landing block:

    /**
     * Set whether the butterfly has landed.
     * @param landed TRUE if the butterfly has landed.
     */
    public void setLanded(boolean landed) {

        // Don't repeat this otherwise the butterflies fall
        if (!this.getIsLanded() && landed) {
            switch (this.getLandedDirection()) {
                case DOWN -> this.setPos(this.getX(), Math.floor(this.getY()), this.getZ());
                case UP -> this.setPos(this.getX(), Math.floor(this.getY()) + 0.9, this.getZ());
                case NORTH -> this.setPos(this.getX(), this.getY(), Math.floor(this.getZ()));
                case SOUTH -> this.setPos(this.getX(), this.getY(), Math.floor(this.getZ()) + 0.9);
                case WEST -> this.setPos(Math.floor(this.getX()), this.getY(), this.getZ());
                case EAST -> this.setPos(Math.floor(this.getX()) + 0.9, this.getY(), this.getZ());
            }
        }

        entityData.set(DATA_LANDED, landed);
    }

With this, butterflies can land on walls, ceilings, and everything in between, but I still need a way to get them there.

Cheating Early


Minecraft’s default flying pathfinding is not great for butterflies. When trying to land, butterflies often get stuck trying to fly through trees instead of around them. I considered writing a custom path navigator, but instead I decided to cheat: if there’s a valid landing block nearby, I just tell the Goal it has reached its target.

To achieve this I override the isReachedTarget() method in ButterflyRestGoal:

    /**
     * Overrides to return TRUE if any valid block is below the butterfly.
     * @return TRUE if the butterfly can land.
     */
    protected boolean isReachedTarget() {
        Level level = this.butterfly.level();
        BlockPos position = this.butterfly.blockPosition();

        //  Land on top of a block.
        if (this.isValidTarget(level, position.below())) {
            this.blockPos = position.below();
            this.butterfly.setLandedDirection(Direction.DOWN);
            return true;
        }

        //  Land underneath a block.
        if (this.isValidTarget(level, position.above())) {
            this.blockPos = position.above();
            this.butterfly.setLandedDirection(Direction.UP);
            return true;
        }

        // Land north of a block
        if (this.isValidTarget(level, position.north())) {
            this.blockPos = position.north();
            this.butterfly.setLandedDirection(Direction.NORTH);
            return true;
        }

        // Land south of a block
        if (this.isValidTarget(level, position.south())) {
            this.blockPos = position.south();
            this.butterfly.setLandedDirection(Direction.SOUTH);
            return true;
        }

        // Land east of a block
        if (this.isValidTarget(level, position.east())) {
            this.blockPos = position.east();
            this.butterfly.setLandedDirection(Direction.EAST);
            return true;
        }

        // Land west of a block
        if (this.isValidTarget(level, position.west())) {
            this.blockPos = position.west();
            this.butterfly.setLandedDirection(Direction.WEST);
            return true;
        }

        return false;
    }

To complement this, I simplify isValidTarget() so it only checks to see if the specified block is a valid landing block. There were some extra checks from an earlier attempt to fix the pathfinding issues, but with the above cheat code, they aren’t needed anymore.

    /**
     * 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 this.butterfly.isValidLandingBlock(levelReader.getBlockState(blockPos));
    }

This “cheat” solves most pathfinding issues. They aren’t always getting stuck on trees anymore, since any leaf block will do, not just the specific one they targeted.

Slow Down! Stop!


One problem I was still having with the goal at this stage was that butterflies kept moving after they had landed. The pathfinding isn’t stopped in the base class when a mob reaches its target.

While this works fine for normal mobs, I needed butterflies to come to a full stop when they land. So I overrode the tick() method and made sure to stop() the navigation the moment it reaches its goal.

    /**
     * Update the butterfly after it has landed.
     */
    @Override
    public void tick() {
        if (!butterfly.getIsLanded()) {
            if (this.isReachedTarget()) {
                --this.tryTicks;
                this.mob.getNavigation().stop();
                this.butterfly.setLanded(true);
            } else {
                ++this.tryTicks;
                if (this.shouldRecalculatePath()) {
                    moveMobToBlock();
                }
            }
        }
    }

This give us full control over the butterfly: they will reach their destination and position themselves properly. But they still don’t face in the right direction.

Spinning the Right Way


Rather than having the mob actually rotate in world, I just rotate it in the renderer. This is much simpler to do and achieves the same effect. I add a method to the renderer that rotates the butterfly based on the direction of its landing block.

    /**
     * Rotate the butterfly if it is landed.
     * @param entity The entity to render the information for.
     * @param poseStack The pose stack.
     */
    protected void rotateIfLanded(@NotNull T entity,
                                  @NotNull PoseStack poseStack) {

        if (entity.getIsLanded()) {
            switch (entity.getLandedDirection()) {
                case UP -> poseStack.mulPose(Axis.XP.rotationDegrees(180.f));
                case NORTH -> poseStack.mulPose(Axis.XP.rotationDegrees(90.f));
                case SOUTH -> poseStack.mulPose(Axis.XP.rotationDegrees(-90.f));
                case EAST -> poseStack.mulPose(Axis.ZP.rotationDegrees(90.f));
                case WEST -> poseStack.mulPose(Axis.ZP.rotationDegrees(-90.f));
                default -> {
                }
            }
        }
    }

This is used by both ButterflyRenderer and HummingbirdMothRenderer, letting butterflies and moths appear to cling to walls and ceilings as if they’d always lived there.

Speaking of Hummingbird Moths…


This week I also added some code to give hummingbird moths a landing “animation” as well. Basically, they will rotate slight and stop flapping their wings. Now they can land properly, giving them a little bit more realism.

All of these changes are live in the latest version of the mod. So if you’ve ever wanted to see butterflies perched sideways on a tree or chilling under an overhang like a weird little bat, now you can. Go update and check it out!