Housing the Lepidopterist

We have job blocks. We have villagers. What we don’t have is a home for these new villagers to live. This week I’ve added new buildings that will spawn in villages. And with a new home, lepidopterists can spawn naturally as well.

Creating the Buildings


I wanted to add buildings to support the new profession. I wanted these buildings to spawn as a part of a village, and to have a vanilla feel to them so they fit in. Since butterflies don’t spawn in desert or snow, there were only three variants of the building I needed to add: plains, savanna, and taiga.

To add structures to Minecraft you can use structure blocks to save a structure out to an NBT file. You won’t find these in the Creative Inventory, but you can access them using an in-game command:

/give @a minecraft:structure_block

Using these blocks you can load and save structures from NBT files. When creating structures to be added to villages, it’s a good idea to base the structure off one that already exists. This way, you can ignore jigsaw blocks, blocks that tell Minecraft how to stitch things together, and just use the ones already in place.

In LOAD mode, the block will first show you the bounding box. Once you make sure the area is clear, click LOAD again to load the actual structure. To find out the names of structures in the game, you can look in the external dependencies of your project for net.minecraft:client:extra.

I loaded in the shepherds houses for the three biomes to use as a basis for my custom buildings. I decorated them with flowers, ensured they had a butterfly feeder in them, and added a bottled butterfly to each for good measure. Once I had structures I was happy with, I use the structure blocks to save the new NBTs.

I make sure that Include Entities is set to ON, since this will save the butterfly in the bottle as well. Now that I have the structures, it’s time to actually add them to the game.

Modifying the Structure Pools


To add the new buildings to the mod, we need to add them to the structure pools already in place. We can do this by accessing the registry for the template pools and insert our new structures where appropriate. I implemented a class that responds to the TagsUpdatedEvent and adds the new buildings to the village pools.

/**
 * Listens for generic events on the Forge Bus.
 */
public class ForgeEventListener {

    /**
     * Construction
     * @param forgeEventBus The event bus to register with.
     */
    public ForgeEventListener(IEventBus forgeEventBus) {

        forgeEventBus.register(this);
        forgeEventBus.addListener(this::onTagsUpdated);
    }

    /**
     * Add the lepidopterist's buildings to villages.
     * @param event The event we respond to in order to add the villages.
     */
    private void onTagsUpdated(TagsUpdatedEvent event)
    {
        if(event.getUpdateCause() == TagsUpdatedEvent.UpdateCause.SERVER_DATA_LOAD) {

            // Plains
            addToPool(event.getRegistryAccess(),
                    new ResourceLocation("village/plains/houses"),
                    new ResourceLocation("butterflies", "village/plains/houses/plains_butterfly_house_1"),
                    3);

            // Savanna
            addToPool(event.getRegistryAccess(),
                    new ResourceLocation("village/savanna/houses"),
                    new ResourceLocation("butterflies", "village/savanna/houses/savanna_butterfly_house_1"),
                    2);

            // Taiga
            addToPool(event.getRegistryAccess(),
                    new ResourceLocation("village/taiga/houses"),
                    new ResourceLocation("butterflies", "village/taiga/houses/taiga_butterfly_house_1"),
                    2);
        }
    }

    /**
     * Adds a structure to the specified pool.
     * @param registryAccess Access to the registry, provided by the event.
     * @param structurePool The RL of the pool to add to.
     * @param structureToAdd The RL of the structure to add.
     * @param weight The likelihood this building will appear.
     */
    private static void addToPool(RegistryAccess registryAccess,
                                  ResourceLocation structurePool,
                                  ResourceLocation structureToAdd,
                                  int weight)
    {
        Registry<StructureTemplatePool> registry = registryAccess.registryOrThrow(Registries.TEMPLATE_POOL);
        StructureTemplatePool pool = Objects.requireNonNull(registry.get(structurePool), structurePool.getPath());

        if(!(pool.rawTemplates instanceof ArrayList)) {
            pool.rawTemplates = new ArrayList<>(pool.rawTemplates);
        }

        SinglePoolElement addedElement = SinglePoolElement
                .single(structureToAdd.toString())
                .apply(StructureTemplatePool.Projection.RIGID);

        pool.rawTemplates.add(Pair.of(addedElement, weight));

        for (int i = 0; i < weight; ++i) {
            pool.templates.add(addedElement);
        }
    }
}

The addToPool() method is based on Kaupenjoe’s Tutorial, which has proven to be a good resource a few times already. My implementation is slightly different, but it does the same thing. It gets the pool we want to modify, then it adds the specified structure to the pool, taking into account the weight we’ve given the structure.

In our event response we add the buildings I designed to the appropriate village pools. Now, when a world is generated, there is a chance these villages will have the new buildings. But there is still a problem to resolve.

Access Transformers

In StructureTemplatePool, both templates and rawTemplates are private. However the only way we can implement this feature is to access them. How to get around this?

The answer is to use access transformers. These will allow us to change, or transform, the access level of parts of the Minecraft source code. To do this we need to add, or uncomment, a line from build.gradle.

    // This property enables access transformers for use in development.
    // They will be applied to the Minecraft artifact.
    // The access transformer file can be anywhere in the project.
    // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
    // This default location is a best practice to automatically put the file in the right place in the final jar.
    // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.
    accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')

Now we need to add the transformer.cfg file and give ourselves access to the private properties. This is simple enough to do.

public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool f_210560_ # templates
public-f net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool f_210559_ # rawTemplates

Using public we can make the property public. Using public-f we also remove the final modifier from the property. Next is the full package path, in this case we want to modify StructureTemplatePool.

Then we have those weird f_ followed by random numbers. These are known as SRG names, and are needed so the mod knows where in the compiled code these methods will be. To get them in IntelliJ, you can right-click on the property you want to modify and choose Get SRG Name. This will show you the SRG name, and you can use it in this config file.

And after all of that is dealt with, we can compile the mod and search for a village with our new building!

As an addendum, I know this can also be achieved using mixins, however they seem a lot more involved and are probably overkill for what I’m trying to achieve right now. For now, at least, access transformers do what I need them to do.

Bug Fix: Hero of the Village


When I implemented the villager last week, I forgot one step. The lepidopterist needs rewards for when the player becomes a Hero of the Village. Thankfully, this is as easy as adding a single loot table to loot_tables/gameplay/hero_of_the_village/ with the same name as the profession.

{
  "type": "minecraft:gift",
  "pools": [
    {
      "bonus_rolls": 0.0,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "butterflies:silk"
        },
        {
          "type": "minecraft:item",
          "name": "butterflies:butterfly_net"
        }
      ],
      "rolls": 1.0
    }
  ],
  "random_sequence": "minecraft:gameplay/hero_of_the_village/lepidopterist_gift"
}

Now the player can earn some rewards from the lepidopterist alongside other villagers after they fight off an Illager Raid.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.