Porting to 1.21.4: Stage 1

Stage 1 of porting a mod to a new version is to get the thing compiling. Once this is done you can move on to getting the game to launch, managing to create a world, and then a wealth of tests to ensure all the features work. In this article I’m going to talk about some of the things I needed just to get the mod to compile again.

NeoForge has great documentation that helps you update your mod to newer versions. Unfortunately it’s not perfect, and there are a few things that aren’t documented well. Every mod is different, and it can be difficult to cover every single use case.

As I was porting to this latest version of Minecraft, I also spotted some other problems that would affect earlier versions as well. Rather than fixing these right away, I made sure to create new tickets so I would remember to work on them later. The more work I do, the more work it generates…

So, with more ado, let’s get into the major things I needed to fix to get this port started!

Colourless Spawn Eggs


DeferredSpawnEggItem no longer exists as of 1.21.3. Since this is the class that all my spawn eggs used, I needed to fix it. Unfortunately I couldn’t find a replacement, and there was nothing in the documentation about it. I had to go to the NeoForge Discord to find the announcement:

NeoForge 21.3.19-beta introduces a small breaking change: DeferredSpawnEggItem was removed. This was done because in recent versions the “deferred” part isn’t needed anymore as entities are registered before items.

mat-E-robbrt

Basically you have to use the base SpawnEggItem instead. But this also means that you cannot set the colours for the eggs in the constructor anymore. I had a brief discussion in the Discord1 and was told that I either had to set it in the item’s model, but they recommended I create custom textures since the old textures will be removed completely in Minecraft 1.21.5.

Instead of creating the 100+ textures I will need for every butterfly, I opted to stick with the dull gray-white textures for this initial port. But that doesn’t mean I won’t work on some new textures in the future.

Render States


Minecraft 1.21.2 introduced render states, which are go-betweens for the renderer and the game entities. Essentially these classes hold data taken from the entity for the renderer to use later. They are generally PODs (i.e. plain-old-data objects) that just hold some state. As an example, here’s the render state for a Butterfly Model:

/**
 * The render state for a butterfly.
 */
@OnlyIn(Dist.CLIENT)
public class ButterflyRenderState extends LivingEntityRenderState {
    public boolean isLanded;
    public boolean isMoth;
    public float renderScale;
    public String debugInfo;
    public ResourceLocation texture;
}

These are all the values from the Butterfly entity that the renderer uses when rendering a Butterfly. Now, when we want any data from the entity it comes from a single method in the ButterflyRenderer class:

    /**
     * Extracts the render state for use in rendering.
     * @param entity The butterfly entity.
     * @param renderState The current render state.
     * @param partialTick The number of partial ticks.
     */
    @Override
    public void extractRenderState(@NotNull Butterfly entity,
                                   @NotNull ButterflyRenderState renderState,
                                   float partialTick) {
        super.extractRenderState(entity, renderState, partialTick);

        // Only extract debug info if we need it.
        if (ButterfliesConfig.debugInformation.get()) {
            renderState.debugInfo = entity.getDebugInfo();
        }

        renderState.isLanded = entity.getIsLanded();
        renderState.isMoth = entity.getIsMoth();
        renderState.renderScale = entity.getRenderScale();
        renderState.texture = entity.getTexture();
    }

Any rendering methods that used to take an entity, now take the render state instead. They can pull any information they need from the render state instead, which helps to decouple the renderer from the game state, and ensures the data is consistent for the frame that it runs.

No Books?


A single line in the 1.21.2 Primer tells us that ItemBook has been removed:

BookItem, EnchantedBookItem -> DataComponents#WRITTEN_BOOK_CONTENT

This only led to a small fix where I was checking if an item was a book before it could be placed in the Butterfly Microscope. Essentially I compare it to the registry entry instead of using instanceof, which I really should have been doing in the first place:

    /**
     * Tells the game if the item may be placed in the slot.
     * @param itemStack The item stack to test.
     * @return TRUE if the item can be placed in the slot.
     */
    @Override
    public boolean mayPlace(ItemStack itemStack) {
        return itemStack.getItem() instanceof ButterflyBookItem ||
                itemStack.is(Items.BOOK);
    }

Custom Model Data


In Minecraft 1.21.4 there has been a change to Custom Model Data, namely:

net.minecraft.world.item.component.CustomModelData now takes in a list of floats, flags, strings, and colors to use in the custom model properties based on the provided index

One of the things I was using CustomModelData for was to detect when a butterfly book is full. With this change I had to modify both the data that it writes, and the way it is detected to unlock the advancement. I went down a bit of a rabbit hole with this one, since Compound Tags have also been replaced with Data Properties, meaning the whole system of storing an item’s state has changed.

In the end I took advantage of the CUSTOM_DATA property to set when the book had all the butterflies or moths. This solution came out a lot cleaner than the original solution, allowing me to set two seperate flags rather than relying on a bitmask:

        if (numButterflies >= ButterflyData.getNumButterflySpecies()) {
            CompoundTag filledButterfly = new CompoundTag();
            filledButterfly.putBoolean("filled_butterfly", true);
            newBook.set(DataComponents.CUSTOM_DATA, CustomData.of(filledButterfly));
        }

        if (numMoths >= ButterflyData.getNumMothSpecies()) {
            CompoundTag filledMoth = new CompoundTag();
            filledMoth.putBoolean("filled_moth", true);
            newBook.set(DataComponents.CUSTOM_DATA, CustomData.of(filledMoth));
        }

Other Changes


This is by no means an exhaustive list of all the things that have changed between versions. There are other little things I had to modify, such as many methods explicitly taking ServerLevel rather than just Level, and InteractionResultHolder being replaced with an InteractionResult.

The things listed above are just the main things I had trouble with. The documentation helped a lot, and when that failed the Discord community were happy to step up. With all the compile errors fixed I was ready to make a build, but there was one last hurdle…

Toml No More


When I tried to compile the build, gradle kept spitting out an error message complaining that there were two copies of neoforge.mods.toml. This confused me, and I assumed it was a caching error. I found all copies of this file and deleted all of them except for the one in its original place.

And I still got the error. I was missing something. Then it occured to me: was a second toml file being generated by the build itself?

This turned out to be the answer. In the latest version of NeoForge, this file gets generated, so you no longer need it in your project anymore. The actual fix was to delete the original file and let the build generate it.

Once this was done I could compile the mod and start the game. Stage 1 was complete. I could compile my code. But this was only the first step in porting the mod to 1.21.4…

Conclusion


I wanted to show how much work it actually takes to port a mod to different versions. This is only the first article about this, and as you can see, there’s already a lot to talk about. If you ever wonder why a lot of modders don’t port their mods to multiple versions of the game, this is why. It is a lot of work, and some people may simply just not have the time.

Thankfully, I do have the time right now, so a-porting we will go! With this change the mod now supports 7 different versions of Minecraft, ranging from 1.18.2 up to 1.21.4. With Minecraft 1.21.5 around the corner it won’t be long before we’ll be looking at porting it to yet another version.

  1. Seriously, these guys are so helpful. ↩︎

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.