I’ve been adding even more moths to the mod ready for the next release. Some of these have custom behaviours and others don’t. I also contemplate adding butterfly breeding, so that players can artificially increase the number of butterflies in their farms.
Atlas Moth and Carpet Moth
These two moths can both drop silk when their chysalises are killed. The atlas moth is an absolutely stunning moth in real life. I’ve tried to recreate it’s bright red/orange look and it looks cool, but I still don’t think I’ve done this amazing creature justice. The carpet moth is a type of clothes moth, so like that moth it can also use wool blocks as breeding grounds.
Codling Moth
This is the moth that lays caterpillar eggs in your apples. Despite it being one of the more obnoxious moths, at least to us humans, I still tried to make it look like a good one for players to collect. These moths won’t fertilise flowers like other moths and butterflies.
The Codling’s larva is also known as an apple worm. To reflect this I added a new item, the Infested Apple. To do this, I created a loot modifier. In Forge, loot modifiers allow you to modify the vanilla loot tables. You can add new items that could be found in chests, new drops for entities, or anything that uses a loot table. I start by creating a loot table modifier under the loot_modifiers
data directory.
{ "type": "butterflies:oak_leaves_loot", "conditions": [ { "condition": "forge:loot_table_id", "loot_table_id": "minecraft:blocks/oak_leaves" } ] }
This loot table then needs to be referenced in a global_loot_modifiers.json
file under forge/loot_modifiers
.
{ "replace": false, "entries": [ "butterflies:oak_leaves_loot" ] }
To actually use the loot modifer, we have to add it to a registry, the same way as entities, items, blocks, and so on.
/** * Registers any loot table modifiers, used to modify vanilla loot tables. */ public class LootModifierRegistry { // An instance of a deferred registry we use to register items. public static final DeferredRegister<Codec<? extends IGlobalLootModifier>> INSTANCE = DeferredRegister.create(ForgeRegistries.GLOBAL_LOOT_MODIFIER_SERIALIZERS, ButterfliesMod.MODID); // Modifier for oak leaves. public static final RegistryObject<Codec<OakLeavesLootModifier>> OAK_LEAVES = LootModifierRegistry.INSTANCE.register("oak_leaves_loot", OakLeavesLootModifier.CODEC); }
Now we can create the actual loot modifier. This is the simplest this class can be. All it does is create a 0.25% (1/400) chance that the block will also drop an infested apple. This does mean that there is a 1 in 80,000 possibility the leaves could drop both a normal and an infested apple, but I’m okay with those odds.
/** * A loot modifier to add treasure to some chests. */ public class OakLeavesLootModifier extends LootModifier { // The codec that is registered with Forge. public static final Supplier<Codec<OakLeavesLootModifier>> CODEC = Suppliers.memoize(() -> RecordCodecBuilder.create(inst -> codecStart(inst).apply(inst, OakLeavesLootModifier::new))); /** * Construction * @param conditionsIn The conditions needed for this loot modifier to apply. */ public OakLeavesLootModifier(LootItemCondition[] conditionsIn) { super(conditionsIn); } /** * Apply the modification to the loot. * @param generatedLoot the list of ItemStacks that will be dropped, generated by loot tables * @param context the LootContext, identical to what is passed to loot tables * @return The potentially updated loot. */ @NotNull @Override public ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) { RandomSource random = context.getRandom(); if (random.nextInt(400) == 1) { ItemStack stack = new ItemStack(ItemRegistry.INFESTED_APPLE.get()); generatedLoot.add(stack); } return generatedLoot; } /** * Get the codec. * @return The codec. */ @Override public Codec<? extends IGlobalLootModifier> codec() { return CODEC.get(); } }
The infested apple is just a basic item with no actual uses, other than a recipe that allows players to pull out the Codling Larva.
{ "ingredients": [ { "item": "butterflies:infested_apple" } ], "result": { "item": "butterflies:caterpillar_codling" }, "type": "minecraft:crafting_shapeless" }
And, just for fun, I also add a recipe to create an infested apple. It’s worthless, but it should make it easier for people who need to collect every item.
{ "ingredients": [ { "item": "butterflies:caterpillar_codling" }, { "item": "minecraft:apple" } ], "result": { "item": "butterflies:infested_apple" }, "type": "minecraft:crafting_shapeless" }
Diamondback Moth
This is the last moth I’m adding to the mod (for now, at least). This moth likes to feed on fruit, so it can use melons and pumpkins as places to lay their eggs. I tried to represent its frayed wings in this design, giving it a unique look.
Butterfly Breeding
Up until now, butterflies go through a natural life cycle that players can’t interfere with. For the next version I’ve decided to add a simple breeding system. If a butterfly is fed it’s preferred flower, it will gain one more egg. This can only be done if the number of eggs the butterfly currently has is zero, so players can’t just spam a single butterfly. This gives a natural cool off, similar to how other animals work in Minecraft.
To do this I first had to define what counts as food to the butterfly. Originally this was set to nothing, disabling the entire breeding system for the butterfly.
/** * Butterflies can be fed to increase the number of eggs available, * allowing players to breed them as they can other animals. * @param stack The item stack the player tried to feed the butterfly. * @return FALSE, indicating it isn't food. */ @Override public boolean isFood(@NotNull ItemStack stack) { ResourceLocation location = this.getData().preferredFlower(); @SuppressWarnings("deprecation") Item item = BuiltInRegistries.ITEM.get(location); return stack.is(item); }
Since butterflies don’t age, and that breeding works differently for butterflies, I also override the behaviour when a player interacts with a butterfly. Basically, the method checks the item is “food”, and increases the number of eggs to 1
if the butterfly has no eggs left. It is disabled for butterflies with an Egg Multiplier of zero.
/** * Butterflies can be fed their preferred flower, allowing players to breed * them manually. * @param player The player interacting with the entity. * @param interactionHand The hand that is interacting with the entity. * @return The result of the interaction. */ @Override public @NotNull InteractionResult mobInteract(@NotNull Player player, @NotNull InteractionHand interactionHand) { ItemStack itemstack = player.getItemInHand(interactionHand); if (getData().eggMultiplier() != ButterflyData.EggMultiplier.NONE) { if (this.isFood(itemstack)) { if (!this.level().isClientSide && this.getNumEggs() == 0) { this.usePlayerItem(player, interactionHand, itemstack); setNumEggs(1); return InteractionResult.SUCCESS; } if (this.level().isClientSide) { return InteractionResult.CONSUME; } } } return InteractionResult.PASS; }
With this new feature, it should be easier for players to gather and maintain their butterfly population.
Butterfly Improvements
I’ve been adding all these extra behaviours to moths as I’ve been going through, but this has the side effect of making butterflies a little boring by comparison. So I’ve spent some time going back and adding new behaviours to butterflies as well, based on the flavour text that you will find in the Butterfly Book.
Admiral Butterfly
The admiral butterfly likes to perch in sunlit areas, so I’ve made them crepuscular. This means they will mainly be active during twilight hours, and will spend most of the daytime resting on leaves.
Buckeye Butterfly
Buckeyes can use many plants as hosts so, like the diamondback moth, they now have the ability to lay eggs on pumpkins and watermelons.
Cabbage Butterfly
These erratic flyers are now fast butterflies, joining the Chalkhill, Clipper and Rainbow butterflies in their speedy endeavors.
Clipper Butterfly
One concept I had for the mod that I never followed through on was to have one butterfly for each of Minecraft’s 16 colours that are used for dyes, wool, glass, and so on. The clipper butterfly is supposed to come in a variety of colours, so I put in the work and created 15 more variants so that there was one of each colour.
There wasn’t really any code to write for this one, but I did add a function to the Python data generator so I could quickly create placeholder textures. This was useful in this case, since the eggs, caterpillars, and chrysalises for all 16 clipper butterflies will look the same.
# Generates placeholder textures def generate_textures(entries, base): print("Generating textures...") # Get list of files containing input[0] files = [] for (path, _, filenames) in os.walk(os.getcwd()): # We only want json filenames = [f for f in filenames if f.endswith(".png") and base in f] for name in filenames: files.append(pathlib.Path(path, name)) # Loop Start for entry in entries: for file in files: # Get the new filename new_file = pathlib.Path(str(file).replace(base, entry)) # Create the new file if it doesn't exist if entry != base: if not new_file.is_file(): shutil.copy(file, new_file)
This really helped a lot, but I still managed to mess up. Running this function twice ended up generating textures on top of the already generated textures. I ended up with nearly 3,000 extra PNGs in my change that I didn’t need!
Thankfully it wasn’t too hard to delete the extra files, but it’s a reminder that I should rigorously test any automation before I run it on hundreds of files.
Emperor and Hairstreak Butterflies
These butterflies can now use wood as host blocks for their eggs and caterpillars. It’s not just moths that like wood!
Forester Butterflies
These butterflies are friends to cats, and aren’t afraid of them. Likewise, cats won’t attack them, instead choosing to chase other bugs and rabbits. To implement this, I first created a new method that would also ignore cats.
/** * Check if the butterfly is scared of this entity. Used by foresters, so * they aren't scared of cats. * @param entity The entity that is too close. * @return TRUE if butterflies are scared of the entity. */ private static boolean isNotScaredOfCats(LivingEntity entity) { return !(entity instanceof Butterfly || entity instanceof Caterpillar || entity instanceof ButterflyEgg || entity instanceof Chrysalis || entity instanceof Cat); }
Now we just modify the addGoals()
method so that the AvoidEntityGoal
uses this function instead of the default one for forester butterflies.
// Forester butterflies are not scared of cats. if (Objects.equals(getData().entityId(), "forester")) { this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, LivingEntity.class, 3, 0.8, 1.33, Butterfly::isNotScaredOfCats)); } else { this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, LivingEntity.class, 3, 0.8, 1.33, Butterfly::isScaredOfEverything)); }
We use a similar method to prevent cats from attacking forester butterflies, by first creating a method that can be used by its goal.
/** * Used to stop cats from attacking forester butterflies. * @param entity The entity the cat wants to target. * @return TRUE if the entity is any butterfly except for a Forester. */ private static boolean isButterflyAttackableByCat(LivingEntity entity) { if (entity instanceof Butterfly butterfly) { return !Objects.equals(butterfly.getData().entityId(), "forester"); } return false; }
Then we can just update the event listener that adds butterflies to the target selection so that it uses this method as a predicate.
// Cat if (event.getEntity() instanceof Cat cat) { cat.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>( cat, Butterfly.class, false, EntityEventListener::isButterflyAttackableByCat)); }
Now cats and forester butterflies will ignore each other, so they are safe butterflies to have if you have any furry friends nearby.
Heath Butterflies
Heath butterflies prefer to stay in the light if they can, so I changed them to use the same wander goal as moths.
// Heath butterflies and moths are drawn to light. if (getData().type() == ButterflyData.ButterflyType.MOTH || Objects.equals(getData().entityId(), "heath")) { this.goalSelector.addGoal(8, new MothWanderGoal(this, 1.0)); } else { this.goalSelector.addGoal(8, new ButterflyWanderGoal(this, 1.0)); }
Monarch Butterflies
To reflect their poisonous nature, monarch caterpillars can be brewed into poisonous potions. Unlike other recipes, potion recipes aren’t data driven. So I’ve added a common setup event listener that registers a new brewing recipe.
/** * Common setup event where we register brewing recipes. * @param event The event class. */ private void commonSetup(FMLCommonSetupEvent event) { int monarchIndex = Arrays.asList(ButterflySpeciesList.SPECIES).indexOf("monarch"); BrewingRecipeRegistry.addRecipe( Ingredient.of(PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD)), Ingredient.of(ItemRegistry.CATERPILLAR_ITEMS.get(monarchIndex).get()), PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.POISON)); }
Since we don’t have the Butterfly Data loaded in when recipes are set up, I use the Species List to get the butterfly index here.
Peacock Butterflies
These butterflies love poisonous plants, so can be used to pollinate sweet berry bushes. This was simple enough to implement. All I needed to do was to update the getFlowerBud()
method to include sweet berry bushes.
if (flowerBlock == Blocks.SWEET_BERRY_BUSH) { return Blocks.SWEET_BERRY_BUSH; }
I also changed the order of checks in the pollination goal so that it will allow blocks other than flowers to be valid if it is set in the butterfly data.
/** * 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.above()).isAir()) { return false; } BlockState blockState = levelReader.getBlockState(blockPos); // If this is the butterfly's preferred flower it is always valid. if (blockState.is(this.preferredFlower)) { return true; } if (blockState.is(BlockTags.SMALL_FLOWERS)) { // Butterflies will only fly to other flowers 50% of the time. return (this.random.nextInt() % 2 == 0); } return false; }
Now all that needs to be done is to update the peacock butterfly’s data so that they prefer sweet berry bushes, and they can be used to pollinate these poisonous plants. It also means that players can use sweet berry bushes to breed these butterflies.
A Few More Left
Before I release the next version of the mod, I want to add a few more butterflies. I’ve been travelling Laos, and there are so many amazing butterflies in the country. I’m leaving Laos at the time of writing this, so it seems a good time to add some of the butterflies I’ve spotted to the mod.