It’s always worth taking some time to focus on the details. Small things can make a huge difference to a mod. I spent some time this week working on small features and little improvements. In the long run, these things will make a huge difference.
Locating Resources
I mentioned in my last diary that I should be using ResourceLocations
more. As I was working on other features, I realised that refactoring this would make implementing things easier in the future. So I went ahead and refactored a few things to make better use of ResourceLocations
.
I started by rewriting the entityIdToIndex()
method so that it would work with any string that could be passed into it. I also made it private so that it couldn’t be accessed directly. This helped me quickly locate places in code where it was already being used.
/** * Converts an Entity ID to an index. * @param entityId The entity ID to convert. * @return The index of said entity ID. */ private static int entityIdToIndex(String entityId) { if (entityId.contains(":")) { String[] splits = entityId.split(":"); entityId = splits[1]; } if (entityId.contains("_")) { String[] splits = entityId.split("_"); entityId = splits[0]; } if (ENTITY_ID_TO_INDEX_MAP.containsKey(entityId)) { return ENTITY_ID_TO_INDEX_MAP.get(entityId); } return -1; }
Splitting the string around the underscore (_
) means that it doesn’t matter if we pass in “monarch
” or “monarch_chrysalis
“, we can still obtain the same butterfly index. To actually do a conversion now, we need to access this via a method that takes an actual ResourceLocation
.
/** * Converts a resource location to a butterfly index. * @param location The resource location to convert. * @return The butterfly index for the butterfly species, or -1 if not * found. */ public static int locationToIndex(ResourceLocation location) { return EntityIdToIndex(location.toString()); }
The build would throw errors everywhere I needed to change things now, allowing me to quickly update the mod to use the new method.
Converting an index to a Butterfly entity’s ResourceLocation
works the same as before, except it returns a ResourceLocation
instead of a string. I also added standard methods for generation ResourceLocations
for eggs, chrysalises, and caterpillars.
/** * Gets the resource location for the butterfly egg at the specified index. * @param index The butterfly index. * @return The resource location of the butterfly egg. */ public static ResourceLocation IndexToButterflyEggLocation(int index) { if (INDEX_TO_ENTITY_ID_MAP.containsKey(index)) { return new ResourceLocation(ButterfliesMod.MODID, INDEX_TO_ENTITY_ID_MAP.get(index) + "_egg"); } return null; }
Now there is a standard way to generate ResourceLocations
for these entities that is easy to see at a glance.
After this it was a case of fixing the errors, then going through the entity and item code again to make sure it took advantage of these new methods. I spent some time testing the life cycles and all the items in the mod before I merged this change to the development
branch.
Growing Pains
Butterflies come in different sizes, and the mod represents this by giving us three different sizes of them. But chrysalises and caterpillars are always the same size. Shouldn’t their size vary as well?
It’s a small detail, but one that I think would make a huge difference. So I set about applying size to these entities as well. While I was working on it, the idea popped into my head that they should start small and grow with age. Not only does this add a bit more detail, but it also creates a diegetic way to see how old a caterpillar or chrysalis is.
I started by moving the Size
enumeration from the Butterfly
class to the ButterflyIds
class. I also renamed ButterflyIds
to ButterflyData
so it better represented what it was doing now. Next, I added a new lookup table, and a helper method to allow us to set butterfly data quickly.
private static final Map<Integer, Size> BUTTERFLY_SIZES = new HashMap<>(); /** * Create new butterfly data. * @param index The butterfly index. * @param species The species of the butterfly. Used to generate resource * locations. * @param size The size of the butterfly. */ private static void addButterfly(int index, String species, Size size) { ENTITY_ID_TO_INDEX_MAP.put(species, index); INDEX_TO_ENTITY_ID_MAP.put(index, species); BUTTERFLY_SIZES.put(index, size); }
This makes our static initialization much easier to implement.
static { addButterfly(0, "admiral", Size.MEDIUM); addButterfly(1, "buckeye", Size.MEDIUM); addButterfly(2, "cabbage", Size.LARGE); addButterfly(3, "chalkhill", Size.SMALL); addButterfly(4, "clipper", Size.LARGE); addButterfly(5, "common", Size.MEDIUM); addButterfly(6, "emperor", Size.LARGE); addButterfly(7, "forester", Size.MEDIUM); addButterfly(8, "glasswing", Size.MEDIUM); addButterfly(9, "hairstreak", Size.MEDIUM); addButterfly(10, "heath", Size.SMALL); addButterfly(11, "longwing", Size.SMALL); addButterfly(12, "monarch", Size.MEDIUM); addButterfly(13, "morpho", Size.LARGE); addButterfly(14, "rainbow", Size.SMALL); addButterfly(15, "swallowtail", Size.LARGE); }
This is screaming out loud to be data driven, and is probably something I’ll look at doing in the future. For now it’s less work to keep it hard coded.
To allow access to a butterflies size I implement a couple of helper methods. Buy taking advantage of the locationToIndex()
implemented above, we’re able to get the size of any butterfly entity using any butterfly-based resource location.
public static Size getSize(int index) { if (BUTTERFLY_SIZES.containsKey(index)) { return BUTTERFLY_SIZES.get(index); } return Size.MEDIUM; } public static Size getSize(ResourceLocation location) { int index = locationToIndex(location); return getSize(index); }
In our Chrysalis
and Caterpillar
classes we can now update their getSize()
methods. It applies a small scale based on the caterpillars age, and multiplies that by another scale based on the result of ButterflyData.getSize()
.
/** * Reduce the size of the caterpillar - they are small! * @return The new size of the caterpillar. */ @Override public float getScale() { float scale = (float)getAge() / -24000.0f; scale *= 0.04; scale += 0.08; ResourceLocation location = EntityType.getKey(this.getType()); ButterflyData.Size size = ButterflyData.getSize(location); switch (size) { case SMALL -> { return 0.7f * scale; } case LARGE ->{ return 1.28f * scale; } default -> { return scale; } } }
We now get caterpillars of different sizes, and can tell at a glance which caterpillars are older, and which have just hatched from their eggs.
Butterfly Groves
An oversight I made when I upgraded the mod to version 1.20.1 of Minecraft was that I didn’t update the spawning rules to include cherry groves. Cherry groves make obvious places for butterflies, so I updated the spawn rules so that some butterflies will spawn in them.
Technically this is a bug with the current release, but since it isn’t a mod breaking bug (like the ones detailed last week), I’m okay with keeping this fix for the next release.
Planning
This week I didn’t work on a new feature I was planning. That will have to wait. But as I said at the beginning, it’s always worth taking some time to work on the details.
The ResourceLocation
refactor will make future features easier to implement. The new growth mechanics for caterpillars and chrysalises not only make them more visually interesting, they also provide feedback to the player. Adding butterfly spawns to the cherry groves ensures that the butterflies are integrated with the latest features.
Working on the small stuff makes the entire mod seem a little more thought out in the end, and the hope is that players will appreciate these things.