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 [2026/06/30 12:27] (current) – Add redirect header to new Docs page gauntrecluse
Line 1: Line 1:
-====== Adding Ores to Worlds ====== +~~REDIRECT>https://docs.fabricmc.net/develop/mixins/accessors~~
-A lot of mods add their own ores, and you'll need a way to place them in existing biomes for players to findIn 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 tutorial. Replace references to Quartz Ore with your ore when appropriate.+====== Accessor Mixins ======
  
-==== Adding to the overworld ==== +**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.
-In this sectionour goal will be spawning the ore in the overworld.+
  
-=== Making a ConfiguredFeatures === +The ''@Accessor'' and ''@Invoker'' annotations allow you to access fields or invoke methods that are not visible (private) or mutable (final).
-First we need to create a ConfiguredFeatures. Make sure to register your ConfiguredFeature at ''onInitialize''. Feel free to change the values to suit your mod.+
  
-<code java [enable_line_numbers="true"]> +Unlike typical injectorsaccessors do not prefix the merged methods with the modid of the mod that contains themAdditionally as the **Accessor Mixins** are used in user codethe names of the handlers are not mangledthese differences are important to keep in mind when writing **Accessor Mixins** for compatibility and debugging purposes.
-public class ExampleMod implements ModInitializer { +
-  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''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'' 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.\\ 
-  public void onInitialize() { +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. 
-    Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, new Identifier("tutorial", "ore_quartz_overworld"), ORE_QUARTZ_OVERWORLD);+ 
 +===== Accessor ===== 
 +''@Accessor'' allows you to access or mutate fields. Suppose we want to access ''<yarn field_1752>'' field of ''<yarn class_310>'' class. 
 + 
 +==== 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