User Tools

Site Tools


tutorial:mixin_accessors

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:mixin_accessors [2020/08/29 15:18] – Add how to add ores to the end biomes siglongtutorial:mixin_accessors [2025/11/30 20:08] (current) gauntrecluse
Line 1: Line 1:
-====== Adding Ores to Worlds ====== +====== Accessor Mixins ======
-A lot of mods add their own ores, and you'll need a way to place them in existing biomes for players to find. In this tutorial, we'll look at adding ores to existing biomes. There are 2 steps that are required to add ores to biomes. +
-  * Make a ConfiguredFeatures. This defines how your ore block is spawned. +
-  * Register your feature by using [[tutorial:mixin_introduction|mixin]] into Minecraft class where default features are listed. +
  
-We'll assume you've already created your own ore block at this point. Quartz Ore will serve as our replacement throughout this tutorialReplace references to Quartz Ore with your ore when appropriate.+**Accessor Mixins** are special mixins defined as interfaces which must **only** contain ''@Accessor'' and ''@Invoker'' handlersUnlike normal mixins however, **Accessor Mixins** are accessible via user code directly.
  
-==== Adding to the overworld ==== +The ''@Accessor'' and ''@Invoker'' annotations allow you to access fields or invoke methods that are not visible (private) or mutable (final).
-In this section, our goal will be spawning the ore in the overworld.+
  
-=== Making a ConfiguredFeatures === +Unlike typical injectors, accessors do not prefix the merged methods with the modid of the mod that contains themAdditionally 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.
-First we need to create a ConfiguredFeaturesMake sure to register your ConfiguredFeature at ''onInitialize''. Feel free to change the values to suit your mod.+
  
-<code java [enable_line_numbers="true"]> +''@Accessor''s and ''@Invoker''s 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'' insteadThis is because when the **Accessor Mixin** is merged the handler will remain unchanged and if there is a problem related to the handlerit's useful to know who owns itAdditionallyif 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.\\ 
-public class ExampleMod implements ModInitializer { +Prefixing accessor or invoker handlers is not necessary if they are staticas those may not conflictand the stacktrace provides the Mixin class whose handlers are involved in errors, thus making it sufficient without needing to prefix with the modid.
-  public static ConfiguredFeature<?, ?> ORE_QUARTZ_OVERWORLD = Feature.ORE +
-      .configure(new OreFeatureConfig( +
-        OreFeatureConfig.Rules.BASE_STONE_OVERWORLD +
-        Blocks.NETHER_QUARTZ_ORE.getDefaultState(), +
-        9)) // vein size +
-      .decorate(Decorator.RANGE.configure(new RangeDecoratorConfig( +
-        0// bottom offset +
-        0// min y level +
-        64))) // max y level +
-      .spreadHorizontally() +
-      .repeat(20); // number of veins per chunk+
  
-  @Override +===== Accessor ===== 
-  public void onInitialize() { +''@Accessor'' allows you to access or mutate fields. Suppose we want to access ''<yarn field_1752>'' field of ''<yarn class_310>'' class. 
-    Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new Identifier("tutorial", "ore_quartz_overworld"), ORE_QUARTZ_OVERWORLD);+ 
 +==== Getting a value from the field ==== 
 +<yarncode java> 
 +@Mixin(class_310.class) 
 +public interface class_310Accessor { 
 +    @Accessor("field_1752") 
 +    int modid$getItemUseCooldown(); 
 +
 +</yarncode> 
 + 
 +Usage: 
 + 
 +<yarncode java> 
 +int field_1752 = ((class_310Accessor) class_310.method_1551()).modid$getItemUseCooldown(); 
 +</yarncode> 
 + 
 +==== Setting a value to the field ==== 
 +<yarncode java> 
 +@Mixin(class_310.class) 
 +public interface class_310Accessor 
 +    @Accessor("field_1752"
 +    void modid$setItemUseCooldown(int field_1752); 
 +
 +</yarncode> 
 + 
 +Usage: 
 + 
 +<yarncode java> 
 +((class_310Accessor) class_310.method_1551()).modid$setItemUseCooldown(100); 
 +</yarncode> 
 + 
 +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 ===== 
 +FIXME //VanillaLayeredBiomeSource no longer exists since 1.18this 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 ==== 
 +<code java> 
 +@Mixin(VanillaLayeredBiomeSource.class) 
 +public interface VanillaLayeredBiomeSourceAccessor { 
 +  @Accessor("BIOMES") 
 +  static List<RegistryKey<Biome>> getBiomes() { 
 +    throw new AssertionError();
   }   }
 } }
 </code> </code>
  
-=== Registering the feature === +Usage:
-Vanilla ore features that spawn in the overworld biomes are listed in ''DefaultBiomeFeature.addDefaultOres''. We modify this method to add our ore to the overworld via [[tutorial:mixin_introduction|mixin]].+
  
-<code java [enable_line_numbers="true"]+<code java
-@Mixin(DefaultBiomeFeatures.class) +List<RegistryKey<Biome>> biomes VanillaLayeredBiomeSourceAccessor.getBiomes(); 
-public class DefaultBiomeFeaturesMixin +</code> 
-  @Inject(method = "addDefaultOres(Lnet/minecraft/world/biome/GenerationSettings$Builder;)V", at = @At("TAIL")+ 
-  private static void addDefaultOres(GenerationSettings.Builder builder, CallbackInfo ci) { +==== Setting a value to the field ==== 
-    builder.feature(GenerationStep.Feature.UNDERGROUND_ORES, ExampleMod.ORE_QUARTZ_OVERWORLD);+<code java
 +@Mixin(VanillaLayeredBiomeSource.class) 
 +public interface VanillaLayeredBiomeSourceAccessor 
 +  @Accessor("BIOMES") 
 +  static void setBiomes(List<RegistryKey<Biome>> biomes) { 
 +    throw new AssertionError();
   }   }
 } }
 </code> </code>
  
-=== Result === +Usage:
-You should see quartz ore spawning in the overworld. You can use fill command to remove stone blocks surrounding you like this''/fill ~-4 0 ~-4 ~4 ~ ~4 minecraft:air replace minecraft:stone''.+
  
-{{tutorial:ores.png?800}}+<code java> 
 +VanillaLayeredBiomeSourceAccessor.setBiomes(biomes); 
 +</code>
  
-==== Adding to the end ==== +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 initializersuch as ''42'' or ''"Hello World"''. This is because the compiler inlines constant fields.
-In this section, based on the code in the previous sectionwe will add the ore to the end biomes.+
  
-=== Making a ConfiguredFeatures === +===== Invoker ===== 
-We replace '' OreFeatureConfig.Rules.BASE_STONE_OVERWORLD'' with ''new BlockMatchRuleTest(Blocks.END_STONE)'' because endstone is used as a base block in the end biomes.+''@Invoker'' allows you to access methodsSuppose we want to invoke ''<yarn method_7024>'' method of ''<yarn class_1560>'' class.
  
-<code java [enable_line_numbers="true"]+<yarncode java> 
-public class ExampleMod implements ModInitializer { +@Mixin(class_1560.class) 
-  public static ConfiguredFeature<?, ?> ORE_QUARTZ_END = Feature.ORE +public interface class_1560Invoker { 
-      .configure(new OreFeatureConfig+  @Invoker("method_7024") 
-        new BlockMatchRuleTest(Blocks.END_STONE), /* We use endstone! */ +  boolean modid$invokeTeleportTo(double xdouble y, double z); 
-        Blocks.NETHER_QUARTZ_ORE.getDefaultState()+
-        9)+</yarncode> 
-      .decorate(Decorator.RANGE.configure(new RangeDecoratorConfig( + 
-        0, +Usage: 
-        0+ 
-        64))) +<yarncode java> 
-      .spreadHorizontally(+class_1560 enderman = ...; 
-      .repeat(20);+((class_1560Invokerenderman).modid$invokeTeleportTo(0.0D, 70.0D, 0.0D); 
 +</yarncode> 
 + 
 +===== Invoker for static methods ===== 
 +FIXME //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.
  
-  @Override +<code java> 
-  public void onInitialize() { +@Mixin(BrewingRecipeRegistry.class) 
-    Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new Identifier("tutorial", "ore_quartz_end"), ORE_QUARTZ_END);+public interface BrewingRecipeRegistryAccessor { 
 +  @Invoker("registerPotionType"
 +  static void invokeRegisterPotionType(Item item) { 
 +    throw new AssertionError();
   }   }
 } }
 </code> </code>
  
-=== Registering the feature === +Usage:
-Considering that no ore is generated in the end biomes on vanilla minecraft, ''DefaultBiomeFeature.addDefaultOres'' can't be used for adding an ore to the end biomes. Instead, we inject into ''DefaultBiomeCreator.method_31065'' because this method is used to create every end biomes.+
  
-<code java [enable_line_numbers="true"]+<code java
-@Mixin(DefaultBiomeCreator.class) +BrewingRecipeRegistryAccessor.invokeRegisterPotionType(item); 
-public class DefaultBiomeCreatorMixin +</code> 
-  @Inject(method = "method_31065(Lnet/minecraft/world/biome/GenerationSettings$Builder;)Lnet/minecraft/world/biome/Biome;", at = @At("HEAD")+ 
-  private static void addEndOres(GenerationSettings.Builder builderCallbackInfoReturnable<Biome> cir) { +==== Invoker for constructors ==== 
-    builder.feature(GenerationStep.Feature.UNDERGROUND_ORES, ExampleMod.ORE_QUARTZ_END); + 
-  }+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: 
 +<code java
 +@Mixin(Identifier.class) 
 +public interface IdentifierAccessor 
 +    @Invoker("<init>") 
 +    static Identifier newIdentifier(String namespaceString path) { 
 +        throw new AssertionError(); 
 +    }
 } }
 </code> </code>
  
 +Usage:
 +<code java>
 +Identifier exampleVariable = IdentifierAccessor.newIdentifier("some_namespace", "some_path");
 +</code>
tutorial/mixin_accessors.1598714307.txt.gz · Last modified: 2020/08/29 15:18 by siglong