Butterflies in Mud Puddles

I recently learned about mud-puddling, a behaviour where butterflies and other insects will gather nutrients from puddles, soil, and decaying organic matter. I immediately thought, “That would make a great feature for Bok’s Banging Butterflies.So I set out to implement it.

Mud Puddling


While hiking with some friends near Almaty, we came across a cluster of butterflies fluttering around a puddle. I didn’t think much of it at the time, but it caught my attention enough that I filmed a short video:

Later, while researching new species for Bok’s Banging Butterflies, I came across an article on puddling and realized that’s exactly what those butterflies were doing.

Naturally, I decided to add this behaviour to the mod.

Design Goals


Before I could implement the behaviour, I had to come up with a basic design. Programming needs to be precise, so I needed to define what counts as a mud puddle, how a butterfly behaves around one, and what benefits it has. I came up with the following:

  • A “mud puddle” is a block of water next to a block of mud in the NESW directions.
    • i.e. it doesn’t count if the mud block is above or below the water
  • When mud-puddling, butterflies will fly to random positions above the mud puddle
  • While mud-puddling, butterflies will extend their lifespan by 1 tick per tick, allowing them to live longer

I thought about mud-puddling giving butterflies an extra egg, similar to how Butterfly Feeders work, but I wanted something distinct. This new feature makes mud puddles a passive way for players to care for their butterflies by simply extending their lifespans.

Implementing the Goal


Implementing this behaviour can be contained entirely within a goal, keeping things modular. I create a ButterflyMudPuddlingGoal class to handle anything to do with butterflies mud-puddling. Here’s the basic setup:

/**
 * Handles butterflies flying around mud puddles to gain nutrients.
 */
public class ButterflyMudPuddlingGoal extends MoveToBlockGoal {

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

    public ButterflyMudPuddlingGoal(Butterfly mob,
                                    double speedModifier,
                                    int searchRange,
                                    int verticalSearchRange) {
        super(mob, speedModifier, searchRange, verticalSearchRange);
        this.butterfly = mob;
    }
}

Defining Conditions


As with other non-rest behaviours, butterflies can only mud-puddle when they are active, so I override canUse and canContinueToUse to reflect this:

    /**
     * Stop using if time of day changes to an inactive time.
     * @return Whether the goal can continue being active.
     */
    @Override
    public boolean canContinueToUse() {
        return this.butterfly.getIsActive() && super.canContinueToUse();
    }

    /**
     * Butterflies can only mud puddle when active.
     * @return TRUE if the butterfly can pollinate right now.
     */
    @Override
    public boolean canUse() {
        return this.butterfly.getIsActive() && super.canUse();
    }

Finding Mud Puddles


I override isValidTarget, which is used by the base class to find a valid target. As in the design, I’m looking for a water block that is next to a wood block.

    /**
     * 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.getBlockState(blockPos).is(Blocks.WATER)) {
            return levelReader.getBlockState(blockPos.north()).is(Blocks.MUD) ||
                    levelReader.getBlockState(blockPos.east()).is(Blocks.MUD) ||
                    levelReader.getBlockState(blockPos.south()).is(Blocks.MUD) ||
                    levelReader.getBlockState(blockPos.west()).is(Blocks.MUD);
        }

        return false;
    }

Hovering


To handle the “hovering” behaviour, I set the butterflies to recalculate their path every 10 ticks (half a second).

    /**
     * Make the butterfly recalculate its path more often.
     * @return TRUE every half second.
     */
    @Override
    public boolean shouldRecalculatePath() {
        return this.tryTicks % 10 == 0;
    }

Then, by overriding the moveMobToBlock method, I make it so that the butterfly moves to a random position above the target block, rather than the target block itself. Since this is recalculated every half second, it creates that hovering behaviour I was going for since I understood what mud-puddling actually was.

    /**
     * Override so the butterfly moves to the specified target block, not the
     * block above it.
     */
    protected void moveMobToBlock() {
        RandomSource random = this.mob.getRandom();
        this.mob.getNavigation().moveTo(
                (double)this.blockPos.getX() + (3.0 * random.nextDouble()) - 1.5,
                (double)this.blockPos.getY() + random.nextDouble() + 1.0,
                (double)this.blockPos.getZ() + (3.0 * random.nextDouble()) - 1.5,
                this.speedModifier);
    }
}

Extending the Lifespan


I want butterflies to experience the benefits of mud-puddling from a decent distance, so I override acceptedDistance to increase the range. This will be used later when we check if the butterfly has reached its target.

    /**
     * Set the accepted distance for mud puddling to provide benefits.
     * @return The distance at which mud puddling works.
     */
    @Override
    public double acceptedDistance() {
        return 2.0;
    }

The isReachedTarget method checks if the butterfly is close enough to the target to get the age reduction benefit. It uses the acceptedDistance method from above, so if I want to change the range I can just alter that value.

    /**
     * Tells the goal if the butterfly is close to the mud puddle.
     * @return TRUE if the butterfly is close enough.
     */
    @Override
    protected boolean isReachedTarget() {
        return this.blockPos.closerToCenterThan(this.butterfly.position(), this.acceptedDistance());
    }

Bringing it All Together


To bring everything together, I override tick and implement my own update method here. Since the butterfly can move in and out of range of the mud puddle, this can cause the tryTicks value to increase and decrease in the base function, leaving them in this behaviour indefinitely. Since I want the butterflies to do other things, I always increase tryTicks here to force it to leave after 1200 ticks (1 minute).

Next I check if the butterfly is close enough to the mud puddle using isReachedTarget (see below), and if it is it reduces its age. Finally I check if the butterfly is ready to move again and set a new target position if it should.

    /**
     * Update the butterfly's movement, reducing their age if they are close to
     * the mud puddle.
     */
    @Override
    public void tick() {

        // Always increase so we actually exit the goal.
        ++this.tryTicks;

        // Reduce age if butterfly is close enough to target.
        if (this.isReachedTarget()) {
            this.butterfly.setAge(this.butterfly.getAge() - 2);
        }

        // Keep altering target position to create "hovering" behaviour.
        if (this.shouldRecalculatePath()) {
            this.moveMobToBlock();
        }
    }

Debugging


The debug information contains the target, current position, and age. I was able to use this to see if the age actually decreases as designed.

    /**
     * Used for debug information.
     * @return The name of the goal.
     */
    @NotNull
    @Override
    public String toString() {
        return "Mud Puddle / Target = [" + this.getMoveToTarget() +
                "] / Position = [" + this.butterfly.blockPosition() +
                "] / Age = [" + this.butterfly.getAge() +
                "]";
    }

Registering the Goal


This completes the Goal, so there is only one thing left to do. I register the goal with the Butterfly class. All butterflies and moths can mud-puddle, so I don’t need any fancy code, I just add the goal. I make it more important than the wander goals, but less important than any others (pollination, mating, laying eggs, and avoiding other entities).

        this.goalSelector.addGoal(4, new ButterflyMudPuddlingGoal(this, 0.8, 8, 8));

This means that if one comes across a mud puddle it will go down to extract its nutrients, but not at the expense of laying eggs or pollinating flowers.

No Mud?


A small addendum. I always port all features to all versions of the mod, but there is a problem with this specific feature. Mud wasn’t introduced into Minecraft until 1.19, meaning that this feature can’t work in 1.18. To get around this, I simply implemented the feature with a different block. So, in 1.18.2 you can use clay blocks in their place.

I guess that means they are “clay-puddling” instead…


And that’s everything needed for this feature. Now you might come across a group of butterflies hovering over some mud as you explore the world. But they aren’t just doing it for aesthetics, they’re gathering nutrients and extending their lives.

I love adding these small details to the mod. It makes everything feel more complete, and brings the butterflies (and moths) to life.

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.