Improving Caterpillar Interaction

This week I’ve been focusing on improving caterpillars. Right now they are very hard for players to come across. However, with a few extra features and tweaks, caterpillars will be just a little bit more interesting.

Moving Caterpillars


I thought it would be interesting to allow players to pick up and move caterpillars. This will allow players better interactions with the new mobs, and also provide them an alternative way of collecting butterflies outside of catching them with a butterfly net.

To do this we need to have an item to represent the caterpillars, so the first thing I did was to create new textures to represent each caterpillar type in the mod. I based the shape of the texture on vague memories of the The Very Hungry Caterpillar, and applied colour to each based on the texture of the actual caterpillar.

Next, we need some models for the caterpillar items. I start by creating a template for the caterpillar items. I base it off the butterfly eggs, but by having a base template we will be able to tweak all the caterpillar items at the same time.

{
  "parent": "butterflies:item/template_butterfly_egg"
}

Now all our caterpillar models can just inherit from this and apply their own textures.

{
  "parent": "butterflies:item/template_caterpillar",
  "textures": {
    "layer0": "butterflies:item/caterpillar/caterpillar_longwing"
  }
}

Adding in localisation strings for the new items, we now have all the assets we need for the new items. It’s time to write the code that will make them work.

The first thing we need is a CaterpillarItem class to represent a caterpillar when it is in a player’s inventory.

/**
 * A class to represent a caterpillar in the player's inventory.
 */
public class CaterpillarItem extends Item  {

    public static final String ADMIRAL_NAME = "caterpillar_admiral";    
    public static final String BUCKEYE_NAME = "caterpillar_buckeye";
    public static final String CABBAGE_NAME = "caterpillar_cabbage";
    public static final String CHALKHILL_NAME = "caterpillar_chalkhill";
    public static final String CLIPPER_NAME = "caterpillar_clipper";
    public static final String COMMON_NAME = "caterpillar_common";
    public static final String EMPEROR_NAME = "caterpillar_emperor";
    public static final String FORESTER_NAME = "caterpillar_forester";
    public static final String GLASSWING_NAME = "caterpillar_glasswing";
    public static final String HAIRSTREAK_NAME = "caterpillar_hairstreak";
    public static final String HEATH_NAME = "caterpillar_heath";
    public static final String LONGWING_NAME = "caterpillar_longwing";
    public static final String MONARCH_NAME = "caterpillar_monarch";
    public static final String MORPHO_NAME = "caterpillar_morpho";
    public static final String RAINBOW_NAME = "caterpillar_rainbow";
    public static final String SWALLOWTAIL_NAME = "caterpillar_swallowtail";

    private final ResourceLocation species;

    /**
     * Construction
     * @param species The species of the caterpillar
     */
    public CaterpillarItem(String species) {
        super(new Item.Properties());

        this.species = new ResourceLocation(ButterfliesMod.MODID, species);
    }
}

The class takes a single parameter: the species of the caterpillar. Later, this will be used when placing the caterpillar in the world. For now, we have enough to be able to register the caterpillar item. I add the items to the ItemRegistry.

    public static final RegistryObject<Item> CATERPILLAR_EMPEROR = INSTANCE.register(CaterpillarItem.EMPEROR_NAME,
            () -> new CaterpillarItem(Caterpillar.EMPEROR_NAME));

While I’m in here I spend some time tidying up the code, but I won’t go into details on that here. The main point is that only actual spawn eggs will be in the Spawn Eggs tab in Creative Mode, and most other items will appear under Natural Blocks.

Now we have new items in the game, its time to add some interactivity. The first thing is to allow players to get the new items by clicking on caterpillars. To do this we first need a new method in ButterflyData to help us get the item’s ResourceLocation.

    /**
     * Gets the resource location for the caterpillar item at the specified index.
     * @param index The butterfly index.
     * @return The resource location of the caterpillar item.
     */
    public static ResourceLocation indexToCaterpillarItemLocation(int index) {
        String entityId = indexToEntityId(index);
        if (entityId != null) {
            return new ResourceLocation(ButterfliesMod.MODID, "caterpillar_" + entityId);
        }

        return null;
    }

With this, we can store the location of the correct caterpillar item when we create a new Caterpillar entity.

    // The caterpillar item location.
    private final ResourceLocation caterpillarItem;

    /**
     * Create a caterpillar entity.
     * @param species The species of the butterfly
     * @param entityType The entity type.
     * @param level The level we are creating the entity in.
     */
    protected Caterpillar(String species,
                          EntityType<? extends Caterpillar> entityType,
                          Level level) {
        super("textures/entity/caterpillar/caterpillar_" + species + ".png", entityType, level);

        ResourceLocation location = new ResourceLocation(ButterfliesMod.MODID, species);
        ButterflyData data = ButterflyData.getEntry(location);
        this.size = data.size;
        this.caterpillarItem = ButterflyData.indexToCaterpillarItemLocation(data.butterflyIndex);
        setAge(-data.caterpillarLifespan);
    }

Finally, we add a method to the class that will create the item if a player “attacks” a caterpillar.

    /**
     * If a player hits a caterpillar it will be converted to an item in their inventory.
     * @param damageSource The source of the damage.
     * @param damage The amount of damage.
     * @return The result from hurting the caterpillar.
     */
    @Override
    public boolean hurt(@NotNull DamageSource damageSource,
                        float damage) {
        if (damageSource.getEntity() instanceof Player player) {
            if (this.level().isClientSide) {
                player.playSound(SoundEvents.PLAYER_ATTACK_SWEEP, 1F, 1F);
            } else {
                this.remove(RemovalReason.DISCARDED);

                Item caterpillarItem = ForgeRegistries.ITEMS.getValue(this.caterpillarItem);
                if (caterpillarItem != null) {
                    ItemStack itemStack = new ItemStack(caterpillarItem);
                    player.addItem(itemStack);
                }
            }

            return true;
        }

        return super.hurt(damageSource, damage);
    }

We play a sound to give the player feedback that something has happened, get the correct item from the ForgeRegistries, and add it to the player’s inventory.

The last thing we need to do is to add the caterpillar back into the world when a player uses the new item. To do this, we return to the CaterpillarItem class and add a new method that creates a caterpillar when a player right-clicks on a leaf block.

    /**
     * Places the butterfly scroll on a block.
     * @param context Contains information about the block the user clicked on.
     * @return The result of the interaction.
     */
    @Override
    @NotNull
    public InteractionResult useOn(@NotNull UseOnContext context) {
        Player player = context.getPlayer();
        if (player != null) {

            BlockPos clickedPos = context.getClickedPos();

            Block block = context.getLevel().getBlockState(clickedPos).getBlock();
            if (!(block instanceof LeavesBlock)) {
                return InteractionResult.FAIL;
            } else {
                if (!context.getLevel().isClientSide()) {
                    Direction clickedFace = context.getClickedFace();
                    Caterpillar.spawn((ServerLevel) context.getLevel(), this.species, clickedPos.relative(clickedFace), clickedFace.getOpposite());
                } else {
                    player.playSound(SoundEvents.SLIME_SQUISH_SMALL, 1F, 1F);
                }

                context.getItemInHand().shrink(1);

                return InteractionResult.CONSUME;
            }
        }

        return super.useOn(context);
    }

On the server we create the caterpillar, and on the client we play a squishy sound effect. We also reduce the size of the stack, which removes the caterpillar from the inventory.

Now players can move these little grubs around. This can make things easier for players that want to build a butterfly enclosure. If they can’t catch a butterfly, they can pick up a caterpillar and move that instead.

Starving Caterpillars


Caterpillars need food. In this mod, food is represented by caterpillars being on leaf blocks. But it is possible for caterpillars to fall off blocks and end up somewhere there isn’t any food. In these cases, the caterpillar should die, rather than create a chrysalis.

I modified the code in the Caterpillar entity’s customServerAiStep method so that this is what would happen if the caterpillar has no food.

                // If the caterpillar is not on a leaf block it will starve instead.
                if (level().getBlockState(surfaceBlockPos).getBlock() instanceof LeavesBlock) {
                    ResourceLocation location = EntityType.getKey(this.getType());
                    int index = ButterflyData.locationToIndex(location);
                    ResourceLocation newLocation = ButterflyData.indexToChrysalisLocation(index);
                    if (newLocation != null) {
                        Chrysalis.spawn((ServerLevel) this.level(),
                                newLocation,
                                this.getSurfaceBlock(),
                                this.getSurfaceDirection(),
                                this.position(),
                                this.getYRot());
                        this.remove(RemovalReason.DISCARDED);
                    }
                } else {
                    this.hurt(this.damageSources().starve(), 1.0f);
                }

Now players can have a small motivation to move caterpillars – they may need saving so they don’t starve to death.

Caterpillar Rewards


With the ability to pick up caterpillars comes an opportunity for advancements. Advancements are not only good for rewarding players, but can also provide hints at what can be done with the mod. So I created two new advancements players can earn by collecting caterpillars.

Our advancement tree is growing!

More?


I have a couple more improvements for caterpillars that I want to work on. First, caterpillars should be able to spawn naturally. As in, they can spawn in without having to be laid by butterflies first. Second, caterpillars could be placed in jars, similar to how we can do this with butterflies.

After these improvements I think we will be done improving caterpillars for now. I still have plans to improve butterfly behaviour in general, improve the way butterfly eggs work, and a potential method of pollenating and farming flowers with butterflies.