This page was only recently pushed out of drafting! Whilst it has gone under feedback and revisions during drafting, it is still possible that there are inaccuracies or issues. Feedback and contributions are appreciated.
When mixing into a class, you may want to create overrides for methods the target inherits from parents, as you would if it were your own class. However, there are specific design patterns that should be implemented to do so for Mixin targets in order to remain as compatible as possible with other mods wanting to do similar modifications. This page will go over both the approach of trying to directly create an override, and how it is problematic; and will then show the two primary design patterns to tackle the problem.
We will use a concrete example based on overriding a method from LivingEntity in CopperGolemEntity in Minecraft 1.21.10 with Yarn mappings.
This page assumes you already have an understanding of injectors and general Mixin usage. Please read the Introduction page and, if you're an absolute beginner, the tutorial for making your first Mixin is recommended to get familiar with basic syntax and concepts of Mixin.
The class CopperGolemEntity does not have an override for LivingEntity#damage(ServerWorld, DamageSource, float), and we want to call ExampleMod#doSomething() everytime a copper golem specifically gets damaged. As the method we're trying to override the behavior of does not exist in CopperGolemEntity, whether that be in source code or bytecode, we can't try to directly inject into it somehow.
We may be tempted to have our Mixin class extend the super of CopperGolemEntity and override like so:
@Mixin(CopperGolemEntity.class) abstract class CopperGolemEntityMixin extends GolemEntity { // Dummy constructor from extending GolemEntity protected CopperGolemEntityMixin(EntityType<? extends GolemEntity> entityType, World wolrd) { super(entityType, world) } @Override public boolean damage(ServerWorld world, DamageSource source, float amount) { ExampleMod.doSomething(); return super.damage(world, source, amount); } }
However, whilst it is an extremely simple approach, this is inherently incompatible with other mods who may want to apply a similar modification. This is because Mixin will attempt to merge both overrides of the same method in the target class; this cannot chain both modifications and will cause two conflicting methods. With a default Mixin configuration, this will result in a hard crash upon application. Instead, to preserve compatibility, we should opt for one of the following solutions:
We can inject a check within the method we wish to functionally override, which will execute our custom operations if the this instance is of the intended subclass. In our case, we could create a Mixin like the following:
@Mixin(LivingEntity.class) abstract class LivingEntityMixin { @WrapMethod(method = "damage") private boolean doSomethingForCopperGolem(ServerWorld world, DamageSource source, float amount, Operation<Boolean> original) { if ((Object) this instanceof CopperGolemEntity) { ExampleMod.doSomething(); } return original.call(world, source, amount); } }
We must cast to Object first to avoid a compiler error, casting to Object and potentially to the target class is often needed to access the this instance.
Remember of course that other injectors than @WrapMethod may be more fitting depending on your specific use-case. This Mixin only covers our specific situation. For example, in some situations it may be better to use @ModifyReturnValue if the intent is to do something with the method's returned value.
This solution has one main downside, however, and it is that we do not have direct access to the subclass's members as we would with a Mixin directly targeting that class. We may also prefer to have modifications targeting the subclass kept to Mixins targeting that class. Furthermore, chaining overrides like this for multiple classes is arguably merssier.
Alternatively, we may use what is known as a submixin pattern. This will require to create two Mixin classes in dedicated files in our project's mixin package. One will target the class containing the method we want to functionally override – we'll call it the “parent” Mixin or the “super” Mixin – and the other will target the class we want to functionally create an override into, which we'll call the “child” Mixin or “sub” Mixin.
For this tutorial, we will call the parent Mixin class LivingEntityMixin, and the child Mixin class CopperGolemEntitySubMixin. These will be in separate files, with both registered in our mixins.json config.
The parent Mixin must create a “dummy”, or “noop” injector, with a protected handler method, which will apply no changes by itself:
@Mixin(LivingEntity.class) abstract class LivingEntityMixin { @WrapMethod(method = "damage") protected boolean overrideForCopperGolem(ServerWorld world, DamageSource source, float amount, Operation<Boolean> original) { return original.call(world, source, amount); } }
Once again, using @WrapMethod is not the only option for this. Use an injector tailored to the modifications you wish to apply within your override.
The child Mixin, or submixin, targets the class we want to override behavior for, and extends the parent Mixin:
@Mixin(CopperGolemEntity.class) abstract class CopperGolemEntitySubMixin extends LivingEntityMixin { }
We then override the dummy handler, by virtue of it being protected, and apply the actual changes in the override. We can treat the override method as a handler corresponding to the injector defined in the superclass.
@Mixin(CopperGolemEntity.class) abstract class CopperGolemEntitySubMixin extends LivingEntityMixin { @Override protected boolean overrideForCopperGolem(ServerWorld world, DamageSource source, float amount, Operation<Boolean> original) { ExampleMod.doSomething(); return super.overrideForCopperGolem(world, source, amount, original); } }
This can allow us to access any field or member within that target class directly, and can be better to keep all modifications we make to that class contained in the child Mixin.
However, due to a bug, the submixin pattern cannot be used if any class within the hierarchy is an interface.