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.
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:
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!
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:
"HEAD"
: the start of the method"INVOKE", target = "the called method"(, ordinal = the index of the invocation)
: a function invocation (required for Redirect)"RETURN"
: any return statement"TAIL"
: the end of the methodWhen 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!).
Here is an example of what Mixin does under the hood:
Add -Dmixin.debug.export=true
to your VM options and check run/.mixin.out
to see your own generated 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