Table of Contents
Accessor Mixins
Accessor Mixins are special mixins defined as interfaces which must only contain @Accessor and @Invoker handlers. Unlike normal mixins however, Accessor Mixins are accessible via user code directly.
The @Accessor and @Invoker annotations allow you to access fields or invoke methods that are not visible (private) or mutable (final).
Unlike typical injectors, accessors do not prefix the merged methods with the modid of the mod that contains them. Additionally as the Accessor Mixins are used in user code, the names of the handlers are not mangled, these differences are important to keep in mind when writing Accessor Mixins for compatibility and debugging purposes.
@Accessors and @Invokers can infer the field/method name if they are called getFieldName / call/invokeMethodName, omitting the requirement for the value attribute to be specified, however, all non-static handlers should be prefixed as so: modid$getFieldName instead. This is because when the Accessor Mixin is merged the handler will remain unchanged and if there is a problem related to the handler, it's useful to know who owns it. Additionally, if another mod decides to create a Duck interface with a method called getFieldName, when that is merged alongside your Accessor Mixin whichever one applies last will overwrite the other and you end up with a conflict that may crash or cause unintended behaviour.
Prefixing accessor or invoker handlers is not necessary if they are static, as those may not conflict, and the stacktrace provides the Mixin class whose handlers are involved in errors, thus making it sufficient without needing to prefix with the modid.
Accessor
@Accessor allows you to access or mutate fields. Suppose we want to access itemUseCooldown field of MinecraftClient class.
Getting a value from the field
@Mixin(MinecraftClient.class) public interface MinecraftClientAccessor { @Accessor("itemUseCooldown") int modid$getItemUseCooldown(); }
Usage:
int itemUseCooldown = ((MinecraftClientAccessor) MinecraftClient.getInstance()).modid$getItemUseCooldown();
Setting a value to the field
@Mixin(MinecraftClient.class) public interface MinecraftClientAccessor { @Accessor("itemUseCooldown") void modid$setItemUseCooldown(int itemUseCooldown); }
Usage:
((MinecraftClientAccessor) MinecraftClient.getInstance()).modid$setItemUseCooldown(100);
When the field is final and you need to set it, use @Mutable as well. Note that this may not work as expected if the field is set to a constant literal value in its initializer, such as 42 or “Hello World”. This is because the compiler inlines constant fields.
Accessor for static fields
VanillaLayeredBiomeSource no longer exists since 1.18, this example cannot be directly replicated on recent versions.
Suppose we want to access the BIOMES field of VanillaLayeredBiomeSource class.
Getting a value from the field
@Mixin(VanillaLayeredBiomeSource.class) public interface VanillaLayeredBiomeSourceAccessor { @Accessor("BIOMES") static List<RegistryKey<Biome>> getBiomes() { throw new AssertionError(); } }
Usage:
List<RegistryKey<Biome>> biomes = VanillaLayeredBiomeSourceAccessor.getBiomes();
Setting a value to the field
@Mixin(VanillaLayeredBiomeSource.class) public interface VanillaLayeredBiomeSourceAccessor { @Accessor("BIOMES") static void setBiomes(List<RegistryKey<Biome>> biomes) { throw new AssertionError(); } }
Usage:
VanillaLayeredBiomeSourceAccessor.setBiomes(biomes);
When the field is final and you need to set it, use @Mutable as well. Note that this may not work as expected if the field is set to a constant literal value in its initializer, such as 42 or “Hello World”. This is because the compiler inlines constant fields.
Invoker
@Invoker allows you to access methods. Suppose we want to invoke teleportTo method of EndermanEntity class.
@Mixin(EndermanEntity.class) public interface EndermanEntityInvoker { @Invoker("teleportTo") boolean modid$invokeTeleportTo(double x, double y, double z); }
Usage:
EndermanEntity enderman = ...; ((EndermanEntityInvoker) enderman).modid$invokeTeleportTo(0.0D, 70.0D, 0.0D);
Invoker for static methods
This example is not fully accurate to latest versions,
registerPotionType exists now within the Builder nested class within BrewingRecipeRegistry
Suppose we want to invoke registerPotionType method of BrewingRecipeRegistry class.
@Mixin(BrewingRecipeRegistry.class) public interface BrewingRecipeRegistryAccessor { @Invoker("registerPotionType") static void invokeRegisterPotionType(Item item) { throw new AssertionError(); } }
Usage:
BrewingRecipeRegistryAccessor.invokeRegisterPotionType(item);
Invoker for constructors
Invokers for constructors must be static, pass “<init>” as the @Invoker annotation attribute, and return the type of the target class.
For example, we will use the Identifier class's private constructor. This is not something that you normally need to do for that class as it has accessible static constructors; it is however a good example for the syntax of a constructor invoker. Using 1.21.1 Yarn mappings:
@Mixin(Identifier.class) public interface IdentifierAccessor { @Invoker("<init>") static Identifier newIdentifier(String namespace, String path) { throw new AssertionError(); } }
Usage:
Identifier exampleVariable = IdentifierAccessor.newIdentifier("some_namespace", "some_path");