Minecraft Modding: A Fabric Demonstration

Intellij (Community Edition)

minecraftdev.org

fabricmc.net

Introductions to Mixins


Authored by Gabe C, 2020

This document assumes you have some knowledge about Java and some basic Minecraft terminology and concepts. If you don't know java, some general programming knowledge may suffice.

This document is licensed under CC BY 2.0 with the exception of the code contained within, which is MIT licensed.

What is a mod?

Short for modification,

"A mod is an alteration by players or fans of a video game that changes one or more aspects of a video game, such as how it looks or behaves. Mods may range from small changes and tweaks to complete overhauls, and can extend the replay value and interest of the game." - Wikipedia


Condensed History of Minecraft modding (Java edition)

This portion is meant to be read as a time killer while you install IntelliJ and the Minecraft Dev plugin so you can follow along with the demonstration. If you're still waiting after reading this and "How fabric minecraft modding works," take a look at "Final Thoughts and Tips"

I have been playing minecraft for 10 years. I have been around for much of what I am about to describe, but I had to jog my memory. Therefore, this information is a combination of memory, google.

Minecraft had multiple stages of development that coincide with their respective stages of modding. I got this idea from reddit: in some ways it is useful to look at modding in terms of these stages. First there was Classic, then there was Indev, infdev, Alpha, Beta and Release. While I can make a decent summary for most of these, Release will be rather lacking as it is a nine year period.

The purpose of this entire document is not supposed to be an all encompassing thesis on the history of minecraft mods and modding, which I am sure I could write 10 pages on and not be a quarter of the way through. Therefore, this passage will discuss the most popular platforms people used to mod, not minecraft modding as a whole.

Pre-classic, Classic

Pre-classic was early prototype/proof of concept builds Notch only initially released videos of.

In classic, there were basically no publicly available client mods, at least that I can find currently. Most modding was done as an exercise in server administration. If there were mods that added new gameplay content, they were passed around in small circles or kept to the individual.

Indev, Infdev

I couldn't find any mods for these versions of the game, either. Though you might be able to find something if you are dedicated enough. One write up I found suggested this was because these development cycles of the game did not have a multiplayer; Notch was busy rewriting the games engine and multiplayer is a large undertaking for later versions.

Alpha

Minecraft modding took off. Minecraft Coder Pack (MCP) came hot off the heels of the Alpha release and made modding extremely accessible. People began adding content to the game with modding.

Server modding became more formalized with the creation of Bukkit. Bukkit was a server mod with some optimizations, but its main appeal was plugins that could be developed by anyone as addons to bukkit. It is difficult to understate Bukkit's role in minecraft history.

I started playing towards the end of this era and in fact my first minecraft experience was modded.

Beta

The community took modding to a whole new level. Some mods just added massive amounts of content and transformed the game into something almost fundamentally different. (See: Aether mod)

With the explosion of modding, there came the problem of intercompatibility. People wanted to install multiple mods at once, which was impossible unless you were willing to merge the code of both mods yourself by hand. Enter the concept of the modloader and Minecraft Forge.

Modloaders provide ways to hook into Minecraft and change the constructs of the code, things like methods, classes and variables, instead of the code itself. Forge was built on top of Risugami’s ModLoader. It was the loader the community picked up and ~~ran~~ absolutely sprinted with.

Optifine also came out towards the end of Beta. Optifine is the most popular minecraft mod of all time, possibly the most popular video game mod off all time.

Release

Forge went under new management and created ForgeModLoader to supersede Risugami’s ModLoader. This made the modding process even simpler for end users and provided more options for the Forge project moving forward.

Forge became the defacto modding platform with literally tens of thousands of minecraft mods.

Fabric mod loader was publicly released in 2018. Much of the modding community seems to be moving to Fabric. While Forge has more mods built for it, Fabric is slowly gaining "market share" as a development platform.


How (fabric) minecraft modding works

MCP is a decompiled and deobfuscated repository of minecraft and fortunately it is compilable and easy to run. It can be used to quickly prototype and create modified versions of the game, however

  • This method requires you modify the minecraft jar
  • It's legally questionable to distribute as you are technically distributing part of Mojang's code
  • No mod compatibility

So, we need a way to modify the minecraft jar at runtime, using only code we write or others allow us to use, in a way that will generally allow interoperability with other mods.

Enter Mixins

Disclaimer: The word "mixin" is extensively exhausted in the next few paragraphs.

"In object-oriented programming languages, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes." - Wikipedia

Mixins in our case are a way of forcing multiple inheritance in a language that doesn't support multiple inheritance. Though, the "inheritance" part isn't usually what mixins are about; mixins are typically about adding extra functionality to classes. It's like a Java interface, but as an abstract class they allow us to have instance data and the other benefits of classes.

Mixins aren't supported in Java - though you can make approximations of them - because multiple inheritance is not supported in Java. This is why the Fabric Loader will be applying our Java mixins at runtime using a third party tool called SpongePowered Mixin.

Using Mixin, mixins are literally applied by changing the bytecode of the Intermediate Language before the JVM processes it. This idea of mixins fits nicely with our need to inject our code wherever we want in whatever methods we want, access private variables, and completely replace or disable certain functionality by referencing Minecraft code. The alternative, as discussed with MCP, is to edit Minecraft code and redistribute it. Therefore mixins, being sort of extensions that we create of the classes in minecraft, allow us to distribute our mods as a standalone program without also distributing Mojang's intellectual property, and has the benefit of being modular. (We can load multiple different mods at once.)

Mixins are a difficult concept to wrap your head around, and this short explanation is what I would consider barely useful. Read it again if it doesn't click. I encourage you read "Introduction to Mixins". Read it multiple times until it clicks, it is worth it.

Mappings

There are two more problems, one big problem: names. If we want to create mixins for our mod, it would be easy if there was access to the names of the classes, methods and variables we need to modify. Fortunately, due to the nature of Java, every construct - that is classes, methods, fields, interfaces, etc - must have names we can view in the IL (exception local variables.) Unfortunately, the minecraft jar is obfuscated for security, file size and intellectual property reasons. This means all the names are useless and random and change every update!

There are tools that can defeat the name changes that happen every update with unknown dark computer magic and give generated static names to these otherwise ever-changing obfuscated names. For instance, the tool sees a field abd in one version, and then sees field abc in its place in another version. Well, it knows those fields are the same thing, so it gives them a stable name called an "intermediary" or "searge", perhaps it will call this field field_1234.

This concept is called mapping. With these intermediary mappings being stable, we know that field_1234 refers to abd. Then we move to another version and know field_1234abc. In other words, field_1234 maps to abc (which is also known as abd in another version, hence the "stable" part).

This saves a lot of reverse engineering time and solves one of our obfuscation problems. Imagine having to interpret that abc and abd meant the same thing every time a new update came out, in JVM IL, in a game with roughly 300k lines of code. Yeah, didn't think so.

With Intermediary, one part of the names problem has been solved, but you may have noticed something. field_1234 IS ABSOLUTELY MEANINGLESS in the context of the code. Does field_1234 mean the player? Is it the players inventory? Does it refer to the sun? Is it a graphical setting?

There is no way to programmatically figure out what field_1234 means. "Meaning" is a human construct that has been stripped from the obfuscated code. Humans must apply meaning back to it. This entails manually combing through code and reverse engineering...a lot of reverse engineering.

This is where Yarn comes in. Hooray, a lot of the reverse engineering has been done for you! Many people have donated their time to give meaning to these meaningless names with...mappings. Yarn is a set of mappings to the intermediary mappings. So if we look at Yarn, we can see that field_1234 means isEntityRemoved (this is just an example, not actual field.) In fact, we never have to see field_1234, because we can just deal exclusively with the isEntityRemoved field transparently as we develop. When we build our mod the mappings will be applied automagically.

Both of these mapping steps, Yarn and Intermediary, occur at different times. When Fabric first launches in a fresh minecraft install, it remaps the entire minecraft jar to Intermediary and stores the result. When we compile our mod, Fabric Loom uses Yarn to map our code to Intermediary. That way, both minecraft and our mod use Intermediary.

Fabric Loader

Our final problem is how to apply the mixins when the game starts.

But first, it should be mentioned that Fabric Loader provides supported procedures to load our mod called entrypoints. The basic process is to add your initializing methods to the "fabric.mod.json" file. These entrypoints can also be used to register event listeners, callbacks and other things. There are multiple entrypoints for main, client, server and prelaunch.

Mixins can also be referenced in our "fabric.mod.json" or in a separate file referenced by fabric.mod.json. The mixins will be automagically applied if they are added in this way.

Fabric API

In its own words "Fabric API is the core library for the most common hooks and intercompatibility measures utilized by mods using the Fabric toolchain."

Basically, Fabric API has a lot of useful and generic features implemented so that we don't have to create them ourselves. This also has the benefit of being standardized, which can help with compatibility.


How TO fabric minecraft mod

Now that there is a basic understanding of the Fabric system itself, a practical demonstration is one of the best ways to further understanding and realize the power of these tools for modding. There is no replacement for experience, so it would be a wise idea to follow along.

Many of our setup steps can be taken care of by an Intellij plugin called MinecraftDev. MinecraftDev provides platforms for many of the most common types of minecraft mod development and is a very user friendly experience.

Mod setup

  1. In IntelliJ, File → New → Project

  2. "Minecraft" in the left panel → select "Fabric Mod" → Next

  3. It doesn't matter much what the build settings are at this moment, just put something in there and avoid capital letters. I chose "mymod" bc naming iz hard.

  4. Choose the Minecraft version you would like to develop for. Generally the loader is version agnostic, so in most cases you should use the latest stable version. Take care what license you select! For our purposes MIT is probably fine. If you decide to develop your own mod, do some research about licensing.

  5. On the next page choose a directory to save the project and click "Finish". At this point you will have to wait a few minutes for the project to be built.

Hot Swap Mixins

Intellij contains support for "hot swap." This allows you to make changes to your code at runtime while the game is running. Due to the nature of SpongePowered mixins, though, we will need to do a little extra setup to get this feature working. I adapted these instructions from the Fabric Wiki page about hot swaps.

Setup

  1. In your Project panel, expand "External Libraries" → net.fabricmc:sponge-mixin:someversion

  2. Right click the .jar → copy → copy path → absolute path

  3. Then, in the top toolbar click "Run" → Edit configurations... → "Application" in the left panel → Minecraft Client → Configuration → expand "VM options"

  4. Create a newline and add a flag javaagent and set it to the path you just copied. It should look like -javaagent:"<path-to-mixin-jar>". Don't forget the quotes.

    image credit fabricmc.net

Now you should be able to hot swap your mixins. Unfortunately hot swaps in general have some limitations:

  • no adding or removing methods
  • no changing method parameters
  • no adding or removing fields

You might have to change your hot swap settings in Intellij in order to use this feature. Go to File → Settings → search "hotswap". Here are mine for reference:

Use

To use this feature, make sure you "Debug" Minecraft Client. Do not "Run", as the feature will be nonfunctional. When you make your necessary changes you want to apply while the game is running, use the hotkey Ctrl+Shift+F9 to compile all affected classes. Once this process completes, a popup will prompt you to reload the classes; click OK. Your changes should have applied.

Exploding Arrows Feature

Now to create an actual mod, we will create a feature that makes arrows explode on impact.

  1. Create a new package called "mixin" under your mod namespace (mymod.mymod in my case).

  2. Create a mixin for ArrowEntity and call it "MixinExplodingArrow_ArrowEntity". The name doesn't particularly matter, I just chose something with a particular convention and clarity. Remember, our mixin class should be abstract. Our target class is defined in the @Mixin annotation.

    @Mixin(ArrowEntity.class)
    public abstract class MixinExplodingArrow_ArrowEntity
    extends PersistentProjectileEntity {
    }
    

    We extend PersistentProjectileEntity because it is the same super as our target class. For more detail on this, read section 6 of "Understanding Mixin Architecture". In a nutshell, since we "apply" the mixin to our target (ArrowEntity), "the mixin's pov is ALWAYS that of the target class!" If we choose a different parent we will produce inconsistent behavior especially when the super keyword is used. You can find the correct parent class to extend by hovering over the target class in your mixin annotation.

    Pro tip: If you didn't unselect the option in setup, the Minecraft jar should have been decompiled using Yarn mappings. You can see exactly how the ArrowEntity class works by Ctrl+Clicking it.

  3. At this point your ide should be shouting at you on the class line. In Intellij, move your cursor to the line and use the default hotkey Alt+Enter. It will generate constructors for you and the error should go away. Notice that these contructors are protected. All mixin constructors are protected or private.

    In general, many problems can "go away" if you hover over something and look at the automatic solutions or choose the first one with Alt+Enter. This is especially true of imports.

    Think for yourself, though. If the automatic solution doesn't work or generates more errors, think about what the errors are telling you and create your own solution.

    Here's what the generated constructors should look like:

    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> entityType, World world) {
        super(entityType, world);
    }
    
    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, double x, double y, double z, World world) {
        super(type, x, y, z, world);
    }
    
    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, LivingEntity owner, World world) {
        super(type, owner, world);
    }
    
  4. Before we get to injecting code and modifying behavior, we need to define that behavior.

    Inside our mixin, define a void explode() method private void explode() {}.

    Now we need to make an explosion within this method. This is really easy as there is a createExplosion method on the world prop which ArrowEntity inherits from a parent. Since the view of our mixin is that of ArrowEntity, we can use this.world.createExplosion. The parameters of this method tell where, what type and how powerful to make the explosion.

    public void explode() {
        this.world.createExplosion(this, this.getX(), this.getBodyY(0.0625D), this.getZ(), 4.0f, Explosion.DestructionType.DESTROY);
    }
    
  5. For our next trick, we will use our mixin to overwrite a method in ArrowEntity. Overwriting methods from our target in mixins is very easy, simply give the method the same signature as the target.

    We should note that overwriting is not the usual, recommended way of doing this because it could theoretically break compatibility with other mods, but it is easy so I am teaching it. We will discuss injection (the typical way to do this) in a few steps. In this case we will overwrite the onHit method, which is called when an arrow hits a LivingEntity, meaning any Minecraft mob. Inside our overwritten method we can call our explode() function to produce an explosion when our arrow hits an entity. Here's what our mixin looks like so far (excluding imports):

    @Mixin(ArrowEntity.class)
    public abstract class MixinExplodingArrow_ArrowEntity extends PersistentProjectileEntity {
        protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> entityType, World world) {
            super(entityType, world);
        }
    
        protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, double x, double y, double z, World world) {
            super(type, x, y, z, world);
        }
    
        protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, LivingEntity owner, World world) {
            super(type, owner, world);
        }
    		@Overwrite // This annotation not required but really helps the programmer
    	  public void onHit(LivingEntity li) {//HERE'S WHAT WE JUST ADDED IN STEP 5
            explode();
        }
        public void explode() {
            this.world.createExplosion(this, this.getX(), this.getBodyY(0.0625D), this.getZ(), 4.0f, Explosion.DestructionType.DESTROY);
        }
    }
    
  6. This should give us a simple feature. The behavior we have added is to make an arrow explode when it hits any LivingEntity. Note that "Living" is a misnomer because it applies to all mobs, not excluding the undead types ie zombies or skeletons. However, before we test this in game we must configure one more feature. Fabric needs to know that this mixin exists. To do this move your cursor to the class name and use Alt+Enter or hover your mouse pointer and click "Add to mixin config." This will add our mixin to the mymod.mixins.json file under the resources folder.

  7. Finally, we can see our mod in action. Click Run → Debug → Minecraft Client and your mod will compile and the game will start up. Fabric should load your mod automatically. Go to "Singleplayer" and configure a new creative world. When you spawn into the new world, grab a bow from your inventory and shoot some mobs. If you can't find any near use a spawn egg or the /summon command.

  1. Let's add some more functionality. We haven't added the most obvious feature, yet. Arrows should explode when they hit blocks! The best place to do that seems to be the tick() method, but that would be a pain. If you view that decompiled method you will see it is long and complicated. Rewriting it in an overridden function and replicating its behavior besides being annoying would be confusing...in that we're supposed to be making a mod but end up implementing vanilla behavior. Not to mention that it is probably legally dubious to be reusing copyrighted code that we decompiled. There is a solution. The same injection that allowed us to overwrite methods in IL can also be a little more...precise. The Inject annotation will allow us to inject our own function inside another function. Here's our new method:

    @Inject(method="tick", at=@At("RETURN"))
    public void explodeOnBlockHit(CallbackInfo ci) {
        if (this.inGround) {
            explode();
        }
    }
    

    In this snippet, we are injecting explodeOnBlockHit before every return statement in tick. In this case the only return is implied at the end of the function, being it is void and there are no early returns. When this method is injecting into tick, it will run every game tick as tick is called and check to see if the arrow hit a block. (That's what this.inGround tells us.)

  2. Stop and rerun Minecraft at this point (again select Debug); this new code can't be hot swapped because it was an addition to a class. Shooting blocks should make an explosion.

    But wait, that's more than an explosion. In fact, there's a hole in my world! (and probably yours too. gotcha)

  3. Since we put our method at the end of the tick method, it checks every game tick to see if the arrow is in the ground. For some reason, arrows are impervious to explosions. When arrows hit the ground, an explosion is created, and the arrow unharmed falls into the blocks the explosion has just uncovered, and creates another explosion. This cycle repeats. We could add a qualifier to check if the arrow has already exploded, but I think it would make more sense to outright remove the arrow. After all, why would the arrow still exist after it explodes?? This is a great time to test out hot swapping. Add this.remove(); to explodeOnBlockHit so it looks like this:

    @Inject(method="tick", at=@At("RETURN"))
    public void explodeOnBlockHit(CallbackInfo ci) {
        if (this.inGround) {
            explode();
            this.remove();
        }
    }
    

    Use the hotkey Ctrl+Shift+F9 to recompile edited classes. After a few seconds a prompt will pop up. Click "Yes" to reload. Tab back to Minecraft. Now your arrows should explode once on impact and disappear. I won't spoil it with a screenshot but I have included the mixin at the bottom of this section for reference.

  4. That's it! It's a functioning mod! If you're aware of how to use the fabric modloader already, you can try it out in your own minecraft installation. When you build the project (use the "build" gradle task), the jar should be in /build/libs.

    If you want some extra fun, mess around with the explosion power ;P

@Mixin(ArrowEntity.class)
public abstract class MixinExplodingArrow_ArrowEntity extends PersistentProjectileEntity {
    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> entityType, World world) {
        super(entityType, world);
    }

    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, double x, double y, double z, World world) {
        super(type, x, y, z, world);
    }

    protected MixinExplodingArrow_ArrowEntity(EntityType<? extends PersistentProjectileEntity> type, LivingEntity owner, World world) {
        super(type, owner, world);
    }

    @Inject(method="tick", at=@At("RETURN"))
    public void explodeOnBlockHit(CallbackInfo ci) {
        if (this.inGround) {
            explode();
            this.remove();
        }
    }
    
    public void onHit(LivingEntity li) {
        explode();
    }
    
    public void explode() {
        this.world.createExplosion(this, this.getX(), this.getBodyY(0.0625D), this.getZ(), 4.0f, Explosion.DestructionType.DESTROY);
    }
}

Final thoughts and tips

More things you can do with Fabric

This was just a small how-to to get you interested. There were lots of things I did not mention.

Mixins, for example. There are many more uses for mixins. You can have multiple mixins. You can have them interact. There are more annotations. Did you know there are also Accessor annotations? And Shadows to access fields in the target class? Or Redirectors? Or other things? You can read more about mixins here and here.

Fabric API has supported ways to do a lot of the most common modding things. Think creating new items, blocks, entities, fluids, dimensions, status effects and event listening. That set of tools probably covers most mods if we're being honest. The API can end up doing a lot of that heavy lifting for you.

Why use Fabric?

So why did I use the Fabric toolchain for this demonstration? There are alternatives that I did briefly mention like Bukkit and Forge, but there are even more alternatives than those.

I like Fabric's light, modular approach. It also has an active community and some very popular mods. In fact, Fabric development is so rapid that 4 months ago Fabric for Minecraft 1.16 was released a mere 2 minutes after Minecraft 1.16 (propogation, technically 8 minutes after "release".)

As u/SkylarSpark put it, "Fabric was created specifically to achieve what Forge couldn't do. 1.13 modded minecraft. Then it grew into this." I would definitely say Fabric lives up to its original intent. To be clear, Forge can currently do 1.13+ modded minecraft. I would just say Fabric does it better. The way minecraft development changed post 1.12 really threw a wrench at Forge which is very old at this point, and I think that held it back in some respects.

On creating your own mods/getting better

Go read the Mixin wiki. Why are you reading the next sentence? I said to read the wiki.

If something isn't working don't be afraid to read and interpret any errors. They may be long and convoluted, but if you think about them for more than 5 seconds you might figure out the issue. If all else fails you can paste the error into google.

For help from people you can join the fabric discord or other communities. You can also message me.

You can also take a look at various github repositories. Many well-written projects are open source and can give you great ideas and teach you more. Here's the Carpet repo; what a beautiful thing.

There are a few youtube tutorials floating around that will teach you, specifically about things I didn't teach you...like using the Fabric API. I can't say there are any up-to-date tutorials I recommend. As of now, it is probably best to stick to the written format for learning these things.

Is Minecraft modding dying?

I think this sentiment comes from nostalgia and no.

Interesting footnotes about Bukkit and Optifine I didn't include above for brevity

You might have noticed I mentioned legality, copyright or intellectual property a couple of times. I've sort of been dancing around these topics for brevity's sake. Turns out I just wanted to put my ideas in writing and this section is officially no longer a footnote.

Bukkit was an open source project started by the mc community. Many of the project creators were hired by Mojang. In their contracts, Mojang took control of the Bukkit project. For some reason, no one mentioned this. Many people donated their time to the Bukkit project thinking they were helping an independent open source community project.

Then, EvilSeph, who had left Mojang in 2013, announced he was discontinuing the Bukkit project for personal reasons in April 2014. EvilSeph was the only developer hired by Mojang who had stayed on the Bukkit project. Mojang shot back basically saying he couldn't do that because they owned the project.

This was huge news to the community. Many people were angered that they had in a way been giving free labor to Mojang and not made aware of it. Wesley Wolfe, who had contributed something like 15,000 lines of code to Bukkit, issued a DMCA takedown. After that tons of contributors just abandoned ship. It was a huge failing of open source.

Fortunately, many people just moved to a fork of Bukkit called Spigot who had found what you could call a copyright loophole that allowed them to continue development.

For a better idea of the impact of these shenanigans, read "The day open source died"

Also, many people have claimed that they remembered Mojang purchasing Bukkit when those devs were hired. I personally don't remember this. It also seemed to be a surprise to most of the people working on the Bukkit project. The confusion likely stems from Mojang wanting to develop an official API since they hired the developers of Bukkit for this purpose. In a post from EvilSeph, he talks about being hired for Mojang:

Now that we have an opportunity to design the official Minecraft API, we intend to make it a suitable replacement for Bukkit, if not a significantly better one, while bukkit.org will remain a community for modders for the foreseeable future.

His post never talks about Mojang acquiring bukkit.

I tried finding EvilSeph out of curiosity. I can only find artifacts of his past presence on the internet. His last tweet was September 2014. I can't find anything on his reddit earlier than 8 years ago, and the trend continues everywhere I look. I could find nothing within the last 5 or so years associated to his real name. Either he is very good at keeping his privacy, or he has stopped using the internet. The last cookie crumb of EvilSeph I could find is a minecraft skin change from 2016.

Anyway, what I get out of this story is if you go the open source route, be transparent. Additionally, know what open source is. Read up on licensing. Five or ten minutes of research into what license you pick for your projects or what you contribute to could for instance protect your intellectual property or save you thousands of dollars down the line.

For those who don't know, Optifine is primarily a performance and shaders mod for Minecraft. It has a long history extending more than 9 years. Probably up to about mc 1.12, basically anyone who was playing mc on a regular basis had Optifine installed. m i l l i o n s. To say that 50% of PC players had at least tried it at one point in time might have been an underestimate. Reportedly, Mojang themselves tried to make a deal to merge OF with the official game, but a deal could not be worked out with sp614x, the developer. It still has millions of people using it, but as a smaller proportion of the pc player base. Though probably not by much. My guess is anyone who is installing the alternatives also has OF installed, and at least goes back to it every once in a while for things like shaders.

So, what do I have to say about OF? Well, as it relates to open source, many people are unhappy that it is closed. In general, the minecraft community leans heavily open source, and going against the status quo makes some people unhappy. Of course it is completely within sp614x's rights to keep the code to OF proprietary. In addition, he offers the mod for free; it's hard to ask for more!

I think the reason some people are mad has to do with OF's progressively lagging development pace. sp614x is a one-man show, after all. I kind of thought of was going to be discontinued just a few months ago. There were only a couple of OF versions available for 1.15, and even those were just previews!

The lack of better improvements and new features from OF has left people wanting to help contribute, but still sp614x does not want to make the project open source. This has left a sort of vacuum, and it is quickly being filled by mods built on Fabric like Sodium, Lithium and Phosphor. These are all modular, open source and receiving rapid community development. I think, though am not sure, that in many cases the Sodium renderer has surpassed OF in terms of performance in many aspects. To me it seems OF only has sheer customizability and shaders going for it. Though even that might change with the development of the Canvas Renderer mod or Iris.

I think if we see developers start contributing to Canvas, shader authors will start moving to Fabric and we may see a slow death of Optifine. It may not even be that difficult to adapt some existing shaders from Optifine to the FREX system that Canvas uses. In fact, my initial point may be moot.

A few days ago coderbot16 made his WIP mod Iris public. Iris is a shaders mod designed to be compatible with traditional OF shaders AND should be compatible with Sodium! Being able to use OF shaders with the benefits of these other performance mods that can't traditionally be run alongside OF, we may see an exodus of players away from OF.

Of course, there will be exceptions like people who are comfortable with OF and it works for them, but the inevitability of Optifine's current trajectory compared to others seems to me to point to a long decline.