Green Skirt Baron Butterfly

Variety is what brings a mod to life, and with every new butterfly, Bok’s Banging Butterflies grows more vibrant. On a recent trip along the Monk’s Trail in Chiang Mai, I spotted an uncommon butterfly known as the Green Skirt Baron. Its vibrant colors and delicate wings struck me, so I decided to bring a bit of that magic to my mod.

I couldn’t find much information about this butterfly, so I reflected that in its design. It would be a simple uncommon butterfly that players could find in the woods. The Baron’s turquoise highlights inspired me to capture this detail as possible in the pixel art, despite the limitations of the small texture.

As shown in the image blue, the butterfly’s bright blue highlights capturing the Green Skirt Baron’s turquoise highlights in pixel art to recreate the essence of the butterfly I saw on the Monk’s Trail. For the other life stages I tried to highlight them with the same turquoise colour that I first noticed on the Green Skirt Baron.

As I added this butterfly, I made a typo in the JSON data. Although the game ran, it reminded me that small mistakes could lead to bigger issues down the line. So I decided to implement some error-checking in the JSON parsing code where we read in the butterfly data that the mod uses. In the end, it will help me catch these minor mistakes before they create major problems.

Improved Error Checking


To improve error checking, I first need a way to detect the error. To do this I modified the searchEnum() method I created in my EnumExtensions. Before it would return a fallback value after attempting to convert the string to an enumerated value. Now, it now throws an exception if it can’t find one instead. This means that we either need to catch it in the surrounding code, otherwise the game will crash.

    /**
     * Convert a string to an enumerated value, ignoring case sensitivity.
     * @param enumeration The Enum class to use.
     * @param search The string to search for.
     * @return The value if found, otherwise the callback.
     * @param <T> The enumerated type.
     */
    @NotNull
    public static <T extends Enum<?>> T searchEnum(Class<T> enumeration,
                                                   String search)
            throws InvalidTypeException {
        for (T each : enumeration.getEnumConstants()) {
            if (each.name().compareToIgnoreCase(search) == 0) {
                return each;
            }
        }

        throw new InvalidTypeException(search);
    }

For the actual error handling, I modify the getEnumValue() in the ButterflyData class. This method is used to parse the values in the Butterfly JSON files and convert them to a valid enum value, and is where we make the call to searchEnum(). By catching the exception here, we have access to all the information we need to generate an error message that tells us exactly where the JSON data is invalid.

        /**
         * Helper method for pulling out enumerated values.
         * @param object The JSON object to read the value from.
         * @param enumeration The enumerated type to extract.
         * @param key The key to look for.
         * @param fallback The fallback value if a value isn't found.
         * @return A value of the enumerated type.
         * @param <T> (Inferred) The type of the enumeration.
         */
        @NotNull
        private static <T extends Enum<?>> T getEnumValue(JsonObject object,
                                                          Class<T> enumeration,
                                                          String key,
                                                          T fallback) {
            // Check the key exists in the JSON object.
            JsonElement element = object.get(key);
            if (element == null) {
                LogUtils.getLogger().error("Element [{}] missing from [{}]",
                        key,
                        object.get("entityId") != null ? object.get("entityId").getAsString() : "unknown");

                return fallback;
            }

            String value = element.getAsString();
            try {
                return EnumExtensions.searchEnum(enumeration, value);
            } catch (InvalidTypeException e) {

                // The value specified is invalid, so make sure it's written to the log.
                LogUtils.getLogger().error("Invalid type specified on [{}] for [{}] of type [{}]:[{}]",
                        object.get("entityId") != null ? object.get("entityId").getAsString() : "unknown",
                        key,
                        enumeration,
                        value);

                return fallback;
            }
        }
    }

The first test checks to see if an element exists. If it doesn’t, then it will log specific details, helping me locate any incorrect values in the JSON data. When creating the error string, I’m sure to wrap any parameters with square brackets “[]“. This is a stylistic choice, but it allows you to easily see if a debug value is missing from the string, something that can help with debugging. It’s something I recommend people do with all log output.

To test the output, I removed the speed key from the Admiral Butterfly’s data file, and I get this error when I load into a world:

Element [speed] missing from [admiral]

This tells us which file to look at (admiral.json), and which key is missing (speed). In the future I’ll be able to check the error logs after loading into a world and it will tell me if any elements are missing.

The second test catches the exception we throw in searchEnum(). In this case we report the value, the key its tied to, and the incorrect value. I ran a test by setting a butterfly’s lifespan to stupidly-long:

Invalid type specified on [unknown] for [caterpillar] of type [class com.bokmcdok.butterflies.world.ButterflyData$Lifespan]:[stupidly-long]

We now know almost everything we need to fix the bug. In this case we don’t actually have an Entity ID, but we can still search for the incorrect value to fix the bug. We know that a caterpillar of type Lifespan is set to an invalid value (stupidly-long). We don’t get the entity ID in this case, however.

The reason we don’t have the right value is because the lifespan values are on a Lifespan object that is a member of the butterfly object. When we are parsing the Lifespan, we don’t have access to the parent object. This is the reason we use a ternary operator when we get the Entity ID:

                        object.get("entityId") != null ? object.get("entityId").getAsString() : "unknown",

Essentially, if we have the Entity ID then we can log it, otherwise just report it as unknown. This means the log output isn’t perfect, but we should still get enough information to find and fix invalid data.

While testing this code, I noticed in the log output that it had detected a couple of errors that had been in the mod for a while:

Two of the butterflies had been using wood for extraLandingBlocks instead of logs. This meant that this feature for these butterflies never actually worked. Thanks to this new code I was able to spot and fix these errors, so it’s already paying off!

Conclusion


Error detection and handling is one of the most important parts of development. I implemented a new butterfly this week, something that I’ve done 33 times before. But with each new butterfly, the mod not only grows in richness but in stability—a reminder that a keen eye for detail benefits both design and development.

Adding the error handling code helps detect some of those details, namely that there were errors that were going undetected. Even though some people help by reporting bugs in the mod, people won’t report something they don’t know is missing or broken, and I can’t fix something I don’t know is broken.

Having code to validate your data and respond if the data is wrong goes a long way toward not only detecting old bugs, but preventing bugs in the future. It’s always a good idea when you come across a bug to implement something that can help you detect and prevent that bug in the future. Adding validation checks not only allows me to detect existing errors, but also serves as a safeguard against future issues. With these mechanisms, I’m able to catch potential bugs early in development.