Generating Butterfly Advancements

I’m still on a mission to reduce the amount of work needed to add new butterflies to the mod. This week I looked at advancements, with the goal of being able to generate their requirements without having to manually edit each one. With this, we are one more step closer to reducing the boiler plate code needed to add a new butterfly.

Advancements in Minecraft aren’t simple structures. They have a lot of parameters, some of which are optional. So the way I opted to do this was to start off with some templates. Basically I just copy/pasted any advancements that referenced butterfly species, and set the criteria and requirements to empty. For example, the template for the Bottle All Butterflies challenge looks like this:

{
  "parent": "butterflies:butterfly/bottle_butterfly",
  "display": {
    "icon": {
      "item": "butterflies:bottled_butterfly"
    },
    "title": {
      "translate": "advancements.butterfly.bottle_all_butterflies.title"
    },
    "description": {
      "translate": "advancements.butterfly.bottle_all_butterflies.description"
    },
    "frame": "challenge",
    "show_toast": true,
    "announce_to_chat": true,
    "hidden": false
  },
  "rewards": {
    "experience": 100
  },
  "criteria": {},
  "requirements": [],
  "base_item": "butterflies:bottled_butterfly_",
  "item_ext": "",
  "requires_all": true
}

At the bottom of the file you’ll see that I added three new attributes. base_item and item_ext tell the generator what should come before and after the butterfly’s name in the criteria. requires_all tells the generator if the achievement requires all of the species or not. In this case we need to bottle all butterfly species so we set it to true.

The generator itself is a simple function that we add to our python script. It opens a template, adds the criteria and requirements, then saves it out to the advancements folder.

ACHIEVEMENTS = "data/butterflies/advancements/butterfly/"
ACHIEVEMENT_TEMPLATES = "data/butterflies/advancements/templates/"


def generate_advancements():
    files = []
    for (_, _, filenames) in os.walk(ACHIEVEMENT_TEMPLATES):
        files.extend(filenames)
        break

    for file in files:
        with open(ACHIEVEMENT_TEMPLATES + file, 'r', encoding="utf8") as input_file:
            json_data = json.load(input_file)

        for butterfly in BUTTERFLIES:
            json_data["criteria"][butterfly] = {
                "trigger": "minecraft:inventory_changed",
                "conditions": {
                    "items": [
                        {
                            "items": [
                                json_data["base_item"] + butterfly + json_data["item_ext"]
                            ]
                        }
                    ]
                }
            }

            if json_data["requires_all"]:
                json_data["requirements"].append([butterfly])

        if not json_data["requires_all"]:
            json_data["requirements"] = [BUTTERFLIES]

        # Remove the extra keys to avoid errors.
        json_data.pop("base_item", None)
        json_data.pop("requires_all", None)
        json_data.pop("item_ext", None)

        with open(ACHIEVEMENTS + file, 'w', encoding="utf8") as output_file:
            output_file.write(json.dumps(json_data,
                                         default=lambda o: o.__dict__,
                                         sort_keys=True,
                                         indent=2))

requires_all is used to structure how we add the criteria to the requirements array. We use pop() to remove the extra keys we added so that they don’t confuse Minecraft when the game loads.

Once this is run with all the templates in place, all our advancements generate for us. Now, when we add new butterflies we don’t need to manually modify the advancements. Just run python script and we’re done!