The butterfly mod is full of bugs, and not always the good kind. This week another bug was reported about Butterfly Scrolls changing after an update. This was a weird one to solve, and unfortunately the fix will only work for scrolls placed in future versions of the mod.
Unintentionally Mutable Scrolls
The Problem
There was a problem with Butterfly Scrolls. You collect them, you place them and you have lots of nice posters displaying all the moths you’ve caught so far. Then you update the mod to a new version, one with a newly added butterfly or moth.
You notice that some of your scrolls have changed. They’re displaying a different butterfly, not the ones you placed before. What is going on?
The Cause
If you’ve been following these development diaries, you’ll know that the basis for a lot of butterfly uses a butterflyIndex
. This index is used to reference into various arrays and data to determine characteristics, behaviours, and visuals for all the species in the mod. But look what happens to an old species butterflyIndex
when I add a new butterfly:

Butterflies and moths are roughly kept in alphabetical order. Thus, when a new species is added, their butterflyIndex
may change as they get shifted down the list.
Normally this isn’t a problem, since all items, blocks, and entities use a string name to determine the butterflyIndex
. When it changes, the code automatically adjusts and everything works fine.
Except, that is, for Butterfly Scrolls. These store a butterflyIndex
that gets saved to the world. They have no idea what species they actually belong to, only the index. So if the index changes between versions, the scrolls will display the wrong butterfly, and drop the wrong item.
The Solution
To solve this, I elected to use the solution that was used for pretty much everything else in the mod. Instead of a single entity type holding a butterflyIndex
, there would be an entity type for each species of butterfly or moth.
I would need to be careful with the fix, however, as I didn’t want to break older versions of the mod that would still have the original entity. So I would write the code to support the old, while overriding the behaviour with the new. This is something I’ve done before, and it keeps transitions to new systems smoother.
To start with, I had to register the new entity types. There would be one for each species, but I would also make sure the old item was registered with the same ID. In this way, the game won’t break if they load into a world that doesn’t have these entities any more.
// The Butterfly Scroll entity. // TODO: Kept for backwards compatibility. This can be removed eventually. private RegistryObject<EntityType<ButterflyScroll>> butterflyScroll; // The Butterfly Scroll entities. private List<RegistryObject<EntityType<ButterflyScroll>>> butterflyScrolls; // <!-- Skipping Code --> /** * Register the entity types. * @param blockRegistry The block registry. */ public void initialise(BlockRegistry blockRegistry) { // Register the old butterfly scroll. this.butterflyScroll = this.deferredRegister.register( ButterflyScroll.NAME, () -> EntityType.Builder.of(ButterflyScroll::create, MobCategory.MISC) .sized(1.0f, 1.0f) .build(ButterflyScroll.NAME)); // Register the new butterfly scrolls this.butterflyScrolls = new ArrayList<>() { { for (int i = 0; i < ButterflyInfo.SPECIES.length; ++i) { add(registerButterflyScroll(i)); } } }; // <!-- Skipping Other Registrations --> }
I also made sure to register the renderers for these new entities so the game knew what they actually look like.
The next step was to override the butterflyIndex
in the ButterflyScroll
entity class. I wrote the function to use the Registry ID, but ensured that it would still return a valid index if it fails. This means that any new scrolls would be based on their type, but old scrolls would still use the butterflyIndex
as before.
/** * Get the index of the butterfly. * @return The butterfly index. */ public int getButterflyIndex() { int result = ButterflyData.getButterflyIndex(this.getType().getDescriptionId()); return result < 0 ? this.butterflyIndex : result; }
I moved the getTextureLocation()
method from the renderer into this class so that it could also take advantage of the new method.
@NotNull public ResourceLocation getTextureLocation() { ButterflyData data = ButterflyData.getEntry(getButterflyIndex()); return data == null ? new ResourceLocation(ButterfliesMod.MOD_ID, "textures/gui/butterfly_scroll/admiral.png") : data.getScrollTexture(); }
Since the texture and item drop both reference this method, all new scrolls will now display the correct texture, or drop the right item even after a new butterfly has been added to the mod.
The only thing left to change was to change the item class so that the new entities are placed instead of the old one. This meant updating the useOn()
method:
/** * 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(); Direction clickedFace = context.getClickedFace(); BlockPos blockPos = clickedPos.relative(clickedFace); ItemStack itemInHand = context.getItemInHand(); if (!this.mayPlace(player, clickedFace, itemInHand, blockPos)) { return InteractionResult.FAIL; } else { Level level = context.getLevel(); int butterflyIndex = getButterflyIndex(); List<RegistryObject<EntityType<ButterflyScroll>>> butterflyScrolls = entityTypeRegistry.getButterflyScrolls(); if (butterflyIndex >= 0 && butterflyIndex < butterflyScrolls.size()) { RegistryObject<EntityType<ButterflyScroll>> entityType = butterflyScrolls.get(butterflyIndex); ButterflyScroll butterflyScroll = new ButterflyScroll(entityType.get(), level, blockPos, clickedFace); // <!-- Snipped remaining code for brevity --> } } return super.useOn(context); }
Now any future scrolls that are placed will use the new entity instead of the old one.
The Remainder
Unfortunately, this fix won’t work on scrolls that have already been placed in earlier version. There isn’t a way around it, since these old scrolls don’t have enough information to fix themselves. So, even though this bug is “fixed”, it will remain in the mod for some time – at least until everyone everywhere replaces their scrolls.
Renaming Things for Moths
Another fix I implemented this week was to rename a lot of items to take into account that moths are now in the mod. All Bottled Moths were called Bottled Butterflies up until now, which was clearly wrong and a bit of an oversight.
To support this renaming I did have to write a small amount of code to override the localisation strings in some items:
/** * Overridden so we can use a single localisation string for all instances. * @param itemStack The stack to get the name for. * @return The description ID, which is a reference to the localisation * string. */ @NotNull @Override public Component getName(@NotNull ItemStack itemStack) { ButterflyData data = ButterflyData.getEntry(butterflyIndex); if (data != null && data.type() == ButterflyData.ButterflyType.MOTH) { return Component.translatable(BOTTLED_MOTH_STRING); } else { return Component.translatable(BOTTLED_BUTTERFLY_STRING); } }
Bottled Caterpillars/Larvae and Butterfly/Moth Scrolls each have similar methods to handle this renaming.


It’s only a small detail, but paying attention to details like this makes a mod seem a lot more complete. And the next thing I’m going to be working on will add a little detail that should make butterflies and moths seem a little bit more realistic.