The Souls of Living Entities

Many cultures believe that butterflies are the souls of the dead. I wanted to represent this idea in the Butterfly Mod. So this week I added the Féileacáin, the Irish spirits of the dead, represented as butterflies.

Being of Irish descent it means something to me to include butterflies from Irish folklore. It creates a connection to my roots, and also something new for players to discover as they explore the world.

Concept and Design


Féileacáin won’t behave exactly like other butterflies. There will be some unique behaviours that will only apply to these spirits. They will be immortal, they will glow in the dark, and as spirits of the dead, they will spawn when a villager dies.

Immortal

The spirits of the dead don’t die, on account of already being dead. So the féileacáin will be immortal and will never die of old age. Likewise, they will never lay eggs or go through the normal life cycle of a butterfly.

Glowing

Féileacáin are bright and will glow in the dark (similar to glow squids). If a player catches one and bottles them, the bottle will provide a new light source for them to use.

Spirits

As spirits of the dead, féileacáin will spawn when a villager dies. They won’t spawn every time a villager dies so as not to overwhelm the world, but they will be a rare spawn for the player to discover. As they spawn when villagers die, players will slowly realise they are spirits of the dead without needing to be told explicitly.

The Butterfly


I start by creating the butterfly data for the new butterfly. These are custom JSON files found under the /resources/data/ that define each butterfly in the mod. You can find a description of each of these attributes in the Butterfly Checklist. I’ll highlight the import ones below.

{
  "breedTarget": "none",
  "diurnality": "nocturnal",
  "eggMultiplier": "none",
  "entityId": "light",
  "extraLandingBlocks": "none",
  "habitat": "none",
  "index": 50,
  "lifespan": {
    "butterfly": "immortal",
    "caterpillar": "short",
    "chrysalis": "short",
    "egg": "short"
  },
  "plantEffect": "none",
  "preferredFlower": "none",
  "rarity": "rare",
  "size": "small",
  "speed": "fast",
  "type": "special"
}

The butterfly is immortal, so I set the lifespan to immortal here. This is a new value for this attribute, and I’ll go into its implementation below. As it is immortal, it shouldn’t lay any eggs, so I set both breedTarget and eggMultiplier to none. This means the butterfly won’t try to mate or lay eggs.

Since féileacáin won’t spawn naturally, I set the habitat to none. In code I add a new enum type and localisation string to support this new value.

For the textures I wanted the Féileacáin to be mostly white, but I added a green tint to it to represent its origins in Irish folklore. I also tried to include skulls in the design – a symbol of their relationship to the dead.

I used placeholder textures for the eggs/caterpillars/chrysalises, since they aren’t supposed to actually spawn in the game.

Immortal Butterflies


Féileacáin are meant to be immortal, so I added a new lifespan to support this. In code this just means I need to modify a couple of enumerations in the ButterflyData, which is the class that stores all the data for butterflies in the mod:

    // Helper enum to determine a butterflies overall lifespan.
    public enum Lifespan {
        SHORT(0),
        MEDIUM(1),
        LONG(2),
        IMMORTAL(3);

        private final int value;

        Lifespan(int value) {
            this.value = value;
        }

        public int getIndex() {
            return this.value;
        }
    }

    // Constants representing the base life spans of each butterfly cycle.
    public static int[] LIFESPAN = {
            24000 * 2,
            24000 * 4,
            24000 * 7,
            Integer.MAX_VALUE
    };

When I set the value of lifespans in the constructor, some of the values get doubled. This is so that we can use the same options for all phases of the butterfly’s life but allow for longer phases as well.

However, in Java multiplying MAX_VALUE leads to truncation errors. This makes sense, since you can’t have a larger value than the largest value. So I have to make a slight modification to ButterflyData‘s constructor when I set the actual lifespan’s value:

        this.butterflyLifespan = butterflyLifespan == Integer.MAX_VALUE ? Integer.MAX_VALUE : butterflyLifespan * 2;

This ensures that we never try and use a value larger than the maximum value.

The last change to ButterflyData is detecting what the butterfly’s lifespan is for both the butterfly book and for actually making it immortal. I do this by modifying getOverallLifeSpan() in ButterflyData:

    /**
     * Get the overall lifespan as a simple enumeration
     * @return A representation of the lifespan.
     */
    public Lifespan getOverallLifeSpan() {
        if (butterflyLifespan == Integer.MAX_VALUE) {
            return Lifespan.IMMORTAL;
        }

        int days = (eggLifespan + caterpillarLifespan + chrysalisLifespan + butterflyLifespan) / 24000;
        if (days < 18) {
            return Lifespan.SHORT;
        } else if (days < 30) {
            return Lifespan.MEDIUM;
        } else {
            return Lifespan.LONG;
        }
    }

Now, I can go to the Butterfly entity class and modify customServerAiStep() so that they won’t die if they’re immortal:

    /**
     * A custom step for the AI update loop.
     */
    @Override
    protected void customServerAiStep() {
        super.customServerAiStep();

        // If the butterfly gets too old it will die. This won't happen if it
        // has been set to persistent (e.g. by using a name tag).
        if (ButterfliesConfig.enableLifespan.get()) {
            if (getData().getOverallLifeSpan() != ButterflyData.Lifespan.IMMORTAL) {
                if (!this.isPersistenceRequired() &&
                        this.getAge() >= 0 &&
                        this.random.nextInt(0, 15) == 0) {
                    this.kill();
                }
            }
        }
    }

Now we have immortal butterflies. The next step is to make them glow.

Glowing Butterflies


First, we want the butterfly to glow in the dark. To do this, I need to set its light level to always be at the maximum. This is how the glow squids work, so I can just use the same method. I create a new renderer that takes all its behaviour from the normal renderer, but sets the light level by overriding getBlockLightLevel()`:

/**
 * Same as the butterfly renderer, except it ensure that the butterfly "glows".
 */
public class GlowButterflyRenderer extends ButterflyRenderer {

    /**
     * Bakes a new model for the renderer
     * @param context The current rendering context
     */
    public GlowButterflyRenderer(EntityRendererProvider.Context context) {
        super(context);
    }

    /**
     * Make the ice butterfly glow.
     * @param butterfly The butterfly.
     * @param blockPos The position of the butterfly.
     * @return Always max light level.
     */
    @Override
    protected int getBlockLightLevel(@NotNull Butterfly butterfly,
                                     @NotNull BlockPos blockPos) {
        return 15;
    }
}

Now, when I register the renderers in the event handling phase I just make sure the Féileacáin uses the GlowButterflyRenderer instead of the base class:

    /**
     * Register the renderers for our entities
     * @param event The event information
     */
    private void onRegisterRenderers(final EntityRenderersEvent.RegisterRenderers event)
    {
        for (RegistryObject<EntityType<? extends Butterfly>> i : entityTypeRegistry.getButterflies()) {
            if (i.getId().compareTo(new ResourceLocation(ButterfliesMod.MOD_ID, "light")) == 0) {
                event.registerEntityRenderer(i.get(), GlowButterflyRenderer::new);
            } else {
                event.registerEntityRenderer(i.get(), ButterflyRenderer::new);
            }
        }

        // <snip>
    }

This means that the butterfly will appear to glow, especially in dark areas or at night.

The next step is to make the bottled butterfly glow in the dark. To do this, I created a registerBottledButterfly() method in the BlockRegistry that sets the light level to 15 if the bottle contains a Féileacáin:

    /**
     * Register a bottled butterfly.
     * @param butterflyIndex The butterfly index to register for.
     * @return The registry object.
     */
    private RegistryObject<Block> registerBottledButterfly(int butterflyIndex) {
        String registryId = getBottledButterflyRegistryId(butterflyIndex);
        BlockBehaviour.Properties properties = BlockBehaviour.Properties.copy(Blocks.GLASS)
                .isRedstoneConductor(BlockRegistry::never)
                .isSuffocating(BlockRegistry::never)
                .isValidSpawn(BlockRegistry::never)
                .isViewBlocking(BlockRegistry::never)
                .noOcclusion()
                .sound(SoundType.GLASS)
                .strength(0.3F);

        // Light Butterflies glow when they are in a bottle.
        if (registryId.equals("bottled_butterfly_light")) {
            properties.lightLevel((blockState) -> 15);
        }

        return deferredRegister.register(registryId, () -> new BottledButterflyBlock(properties));
    }

The rest of the properties haven’t changed, I just moved their declaration here so that they can more easily be modified. Now the mod has a new butterfly-themed light source!

Spirits of the Dead


Féileacáin are the spirits of the dead, and as such they will spawn when villagers die. This spawning is rare, however, so around 1 in every 256 deceased villagers will spawn a Féileacáin. To do this I add a new event listener, that listens for a LivingDeathEvent:

/**
 * Listens for events involving living entities.
 */
public class LivingEventListener {

    /**
     * Construction
     *
     * @param forgeEventBus The event bus to register with.
     */
    public LivingEventListener(IEventBus forgeEventBus) {
        forgeEventBus.register(this);
        forgeEventBus.addListener(this::onLivingDeath);
    }

    /**
     * Fired when a living entity dies.
     * @param event The event data.
     */
    private void onLivingDeath(LivingDeathEvent event) {
        Level level = event.getEntity().level();

        // Only do this on the server.
        if (!level.isClientSide()) {

            // Detect when a villager dies.
            if (event.getEntity() instanceof Villager villager) {

                // 1 in 256 chance.
                if (villager.getRandom().nextInt() % 256 == 1) {

                    // Create a light butterfly.
                    ResourceLocation location = new ResourceLocation(ButterfliesMod.MOD_ID, "light");
                    Butterfly.spawn(level, location, villager.getOnPos(), false);
                }
            }
        }
    }
}

Most of the work here is done by the Butterfly::spawn() method, something I wrote a long time ago. Now, the Féileacáin will spawn on the occasional villager death and gives players another butterfly they can connect.

Next


Next week I plan to work on another feature involving the dead. This one involving Chinese myth rather than Irish, and will add a bit more ambience to the world. Watch this space for more!