Disclaimer: This tutorial is highly simplified and does something very specific that you probably don't want. However, it should give you a basic understanding of the process of creating Mixins.

Mojang and Fabric's APIs can handle a lot for you, but sometimes you'll find limitations. That's where Mixins come in. Mixins let you modify the game's code directly, without editing the Minecraft jar itself.

Localizing Our Target

Let's say you want the player to get flattened if they get hit by an anvil. First, we need to find the code that damages you when an anvil falls on you. Our first clue is that an anvil is a falling block. Searching for that leads us to the FallingBlockEntity class.

Some helpful shortcuts:

  • `Ctrl + N` to search classes (set scope to "All Places")
  • `Ctrl + Shift + F` for full-text search
  • `Ctrl + Click` to follow references
  • Use "Decompile with Vineflower" in a Minecraft class for better code and results

Looking through that class, I found a handleFallDamage function containing this line

this.getWorld().getOtherEntities(this, this.getBoundingBox(), predicate)
    .forEach(entity -> entity.serverDamage(damageSource2, f));

This is our target. Time to modify it!

But first, some theory!

A Mixin is a special Java class that modifies ("mixes in") another class's code while the game is running. Think of it like overlaying the class, adding in some extra code

They are written in Java, annotated with @Mixin(TargetClass.class) and registered in resources/modid.mixin.json.

You can't reference Mixin classes directly from normal code as they don't exist at runtime, but you can add new methods by implementing interfaces in the target class.

Inside Mixins you can annotate functions to do things, in most cases @Inject(...) and @Redirect(...) will do the trick. Both take the following parameters:

(method = "the method to modify in a bytecode format, autocomplete is your friend",
at = @At("INJECTION_POINT"))

Where "INJECTION_POINT" can be:

When injecting your function gets an additional ci(r) parameter this can be used to inject a return statement with ci.cancel() or cir.setReturnValue(...).

Since mixins are applied at runtime, java and your IDE don't know your class will be injected into another one, you cannot access fields or methods.

To work around this you can cast this like so: ((TargetClass) (Object) this) or shadow the member by creating a identically typed field/method and annotating it with @Shadow.

If you need to access a super class you can extend it in your mixin (Make sure your it is abstract!).

Additional reading

Here is an example of what Mixin does under the hood:

Mixin example

Add -Dmixin.debug.export=true to your VM options and check run/.mixin.out to see your own generated code.

Injecting the code

Alright, that was a lot. Feel free to any questions you got in #mc-modding. Now let's finally write our Mixin.

Lets create a new abstract class called FallingBlockEntityMixin inside your mixin directory (create it if it doesn't exist) and annotate it with @Mixin(FallingBlockEntity.class)

A warning should pop, click "Add To Mixin Config" (or manually add "FallingBlockEntityMixin" to the "mixins" array in your config).

This example is unfortunately a special case as we need to inject into a lambda. To find the synthetic method name, go to Menu > View > Show Bytecode, search forserverDamage (as it is called in the lambda) and look up. In this case, the method is named method_32879.

Since we don't care where in the lambda we inject, we'll use "TAIL".

And following that we get this:

@Mixin(FallingBlockEntity.class)
public abstract class FallingBlockEntityMixin {
    @Inject(method = "method_32879", at = @At("TAIL"))
    private static void shrinkEntity(DamageSource damageSource, float amount,
                                     Entity entity, CallbackInfo ci) {
        // The actual code is out of scope for this tutorial (since my mod is already doing it :>)
    }
}

That's it! The rest is up to you, now go make something cool! :D

Back