Table of Contents
This page is currently incomplete and still contains relatively very old edits, it may change at any time or contain incomplete or outdated advice!
Mixin Tips (WIP)
This is a collection of different tips some might find useful. It's recommended to read the intro page to better understand this page.
Extend and implement the target class's parents
When working with Mixins, you may want to use a method from the target's parent on a this
instance. An easy way to make working with this relatively seamless is to directly extend the target class's parents. For instance for the target class:
public class ServerPlayerEntity extends PlayerEntity { //... }
Should have the following Mixin class:
public abstract class SomeMixin extends PlayerEntity { //... }
This page's other sections will showcase the different benefits of also making the Mixin class abstract.
Make abstract classes
It's fair to say that you should never instantiate a mixin class, as Mixin classes exist to be merged. Along with this, it may both be obnoxious and lead to errors and unintended behavior to have to implement all of the methods from your target class's parent interfaces.
Declaring a mixin class abstract doesn't affect its function, and it becomes protected against accidental instantiation and removes the burden of having to implement many of the parent's methods that you don't need:
public abstract class SomeMixin implements TargetsInterfaces { //Does not need to implement TargetsInterfaces' methods. }
Make abstract shadow methods
If you want to access an inaccessible method or field from your target class into your mixin class, you need to use @Shadow
to make that method/field visible. References to those “Shadowed” members will be turned into their target class equivalent when merged.
You can do this as you'd expect within a standard class:
@Shadow protected void hiddenMethod() {/*dummy body*/}
But it's arguably cleaner to use an abstract method (and hence an abstract class):
@Shadow protected abstract void hiddenMethod(); // no dummy body necessary
This doesn't work with private methods, since you can't have private abstract methods, and hence you need to implement a dummy body on those ones.
Access the ''this'' instance of your Mixin
In a Mixin class, if you want to access the this
instance, you have to cast it into Object
and TargetClass
first:
((TargetClass)(Object)this).field/method();
But this can only work if your Mixin class extends and implements everything that the target class does. This may be an issue if one of those classes/interfaces has a method that requires implementation. However, by using an abstract Mixin class, you do not need to implement said methods.
How to mix into inner classes
Non-static inaccessible inner classes and private outer classes
Since you cannot directly access (and hence mention) these classes from outside, you need to use the “targets” field of the @Mixin
annotation to specify the name. This is the same field that you would use to target a private or otherwise inaccessible outer class.
For an inner class, you do this by using the complete name of the outer class, then a $
and finally the name of the inner class, as shown below:
Class:
package some.random.package; public class Outer { private class Inner { public void someRandomMethod() {} } }
Mixin with injection:
@Mixin(targets = "some.random.package.Outer$Inner") public class MyMixin { @Inject(method = "someRandomMethod()V", at = @At("HEAD")) private void injected(CallbackInfo ci) { // your code here } }
The only caveat is that if you want to mixin into the inner class constructor, the first parameter must be of the type of the outer class (this is implicitly added by the compiler to allow the inner class to access instance methods of the outer class):
@Inject(method = "<init>(Lsome/random/package/Outer;)V", at = @At("HEAD")) private void injected(CallbackInfo ci) { // your code here }
Static inaccessible inner classes
These are the same as above, the only difference is that the constructor doesn't have the outer class first parameter (because in static inner classes only private static methods can be accessed from the inner class, and hence that parameter isn't needed).
Anonymous inner classes
These are the same as the static inaccessible inner classes, the only difference is that since they don't have a name, they are declared by appearance order, for example: the anonymous inner class if declared in our previous example class first would be named Outer$1, a second one would be named Outer$2, a third one Outer$3 and so on. It is best to check the Bytecode to be certain of the order of the Anonymous classes if any issues are encountered.
Mixing into lambdas
Sometimes you want to mix into lambdas. However, lambda do not have visible names. In this case, remember that Mixin is applied to bytecode rather than the decompiled source code. Therefore, you can view the bytecode to view the name of lambdas1).
For example, we want to inject the lambda in EntitySelectors
. The target code is seen as follows:
public class EntitySelectorOptions { public static void register() { if (...) { putOption("name", reader -> { // Assume that you want to mixin to here int i = reader.getReader().getCursor(); // ... } // ... } } }
If you directly injects to register
method, that will not include the lambda function. If you view the bytecode, however, you will find:
private static synthetic method_9982(Lnet/minecraft/command/EntitySelectorReader;)V throws com/mojang/brigadier/exceptions/CommandSyntaxException L0 // ...
Therefore, the method name of the lambda you want to mixin should be method_9982
. This is the common way lambdas are found in bytecode, but another format may be lambda$outerMethod$lambdaIndex
. It's best to try and see which synthetic method is invoked in your target method and try to target that.
If you are using the MCDev plugin, its autocomplete for target methods will also include lambdas, and the autocomplete widget will also show parameters for each lambda, which may be leveraged to help confirm which one to target.
Mixing into those not remapped
If you want to mix into classes, methods or fields that are not remapped in yarn, you may try to add remap = false
to avoid potential errors. For example:
@Mixin(value = StringReader.class, remap = false) public abstract class StringReaderMixin { ... }
Another example is:
@Mixin(EntitySelectorReader.class) public abstract class EntitySelectorReaderMixin { @Inject(method = "readTagCharacter", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/StringReader;skipWhitespace()V", remap = false) // your injection method ... }
Changing the remap setting may not fix an issue that would apparently seem to be remapping related, however, so it is best to seek direct support for your specific case.