User Tools

Site Tools


tutorial:mixin_your_first_mixin

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_your_first_mixin [2025/12/30 17:16] – Add missing semicolon in example logger call gauntreclusetutorial:mixin_your_first_mixin [2026/01/23 19:44] (current) – Transition example code from Yarn to MojMaps gauntrecluse
Line 6: Line 6:
 See [[tutorial:mixin_registration|Registering Mixins]] for setting up Mixins on a Fabric project if you intend to follow along with this. See [[tutorial:mixin_registration|Registering Mixins]] for setting up Mixins on a Fabric project if you intend to follow along with this.
  
-This tutorial will be made using a 1.21.1 Fabric Project as reference. It is recommended to develop using IntelliJ IDEa Community Edition to be able to leverage the [[https://mcdev.io/|Minecraft Development plugin]] by demonwav, AKA MCDev. Yarn mappings will be used. If you wish to follow along with any other set of mappings, you may use a tool such as [[https://mappings.dev/|mappings.dev]] or [[https://linkie.shedaniel.dev/mappings|Shedaniel.dev's mappings tool]] to translate between the Yarn mappings showcased and the ones in your development environment as needed.+This tutorial will be made using a 1.21.1 Fabric Project as reference. It is recommended to develop using IntelliJ IDEa Community Edition to be able to leverage the [[https://mcdev.io/|Minecraft Development plugin]] by demonwav, AKA MCDev. Mojang mappings will be used. If you wish to follow along with any other set of mappings, you may use a tool such as [[https://mappings.dev/|mappings.dev]] or [[https://linkie.shedaniel.dev/mappings|Shedaniel.dev's mappings tool]] to translate between the Mojang mappings showcased and the ones in your development environment as needed.
  
 ---- ----
Line 19: Line 19:
  
 After following the necessary steps described in [[tutorial:mixin_registration|Registering Mixins]] to set up our ''mixins.json'' config, we'll create our class in our dedicated Mixin package. We'll call this class ''TutorialFirstMixin'': After following the necessary steps described in [[tutorial:mixin_registration|Registering Mixins]] to set up our ''mixins.json'' config, we'll create our class in our dedicated Mixin package. We'll call this class ''TutorialFirstMixin'':
-<yarncode java>+<code java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
 abstract class TutorialFirstMixin { abstract class TutorialFirstMixin {
  
 } }
-</yarncode>+</code>
  
 It is conventional practice to name your Mixin classes the target class with ''Mixin'' appended as a suffix or prefix. Here, it would typically be named ''MinecraftServerMixin'' instead. This makes is very easy for someone who is looking through your project to know which Mixin classes have a given target. If you, for whatever reason, opt to not follow this convention, it is recommended to still name your Mixins after what they target or aim to modify. It is conventional practice to name your Mixin classes the target class with ''Mixin'' appended as a suffix or prefix. Here, it would typically be named ''MinecraftServerMixin'' instead. This makes is very easy for someone who is looking through your project to know which Mixin classes have a given target. If you, for whatever reason, opt to not follow this convention, it is recommended to still name your Mixins after what they target or aim to modify.
Line 50: Line 50:
  
 The first step, assuming nothing has caused a crash or gone to hell yet, is to create a "stub" method, AKA a method that's just a placeholder for our actual method. This will be the method we annotate with ''@Inject'', and putting it there before the annotation allows us to not be bothered by the ''Annotations are not allowed here'' IDE error from having a lone annotation. The first step, assuming nothing has caused a crash or gone to hell yet, is to create a "stub" method, AKA a method that's just a placeholder for our actual method. This will be the method we annotate with ''@Inject'', and putting it there before the annotation allows us to not be bothered by the ''Annotations are not allowed here'' IDE error from having a lone annotation.
-<yarncode java>+<code java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
 abstract class TutorialFirstMixin { abstract class TutorialFirstMixin {
Line 57: Line 57:
     }     }
 } }
-</yarncode>+</code>
  
 Your Mixin injectors' methods, known as the handler, should always describe what it does rather than what it is. For example, in this tutorial it will be called ''logOnWorldLoad''. We follow this convention to ease debugging as your handler name will appear in stack traces. Your Mixin injectors' methods, known as the handler, should always describe what it does rather than what it is. For example, in this tutorial it will be called ''logOnWorldLoad''. We follow this convention to ease debugging as your handler name will appear in stack traces.
  
 Next, we add the annotation: Next, we add the annotation:
-<yarncode java>+<code java>
 abstract class TutorialFirstMixin { abstract class TutorialFirstMixin {
  
-    @Inject(method = "method_3735")+    @Inject(method = "loadLevel")
     private void logOnWorldLoad() {     private void logOnWorldLoad() {
          
     }     }
 } }
-</yarncode>+</code>
  
 This now specifies to Mixin that, when this method gets merged into ''MinecraftServer'', we want to inject a call to it in the method ''loadWorld''. Since there is only one method of that name in ''MinecraftServer'', the name is enough, this would not be the case if the method had overloads. If you're using the Minecraft Development plugin, you should almost always leverage the plugin's autocomplete for a Mixin annotation's attributes to ensure accuracy. This now specifies to Mixin that, when this method gets merged into ''MinecraftServer'', we want to inject a call to it in the method ''loadWorld''. Since there is only one method of that name in ''MinecraftServer'', the name is enough, this would not be the case if the method had overloads. If you're using the Minecraft Development plugin, you should almost always leverage the plugin's autocomplete for a Mixin annotation's attributes to ensure accuracy.
  
 Now, to specify where in the targeted method we want our injection to happen, we add something that may be a bit counterintuitive to some, an ''@At'' annotation as one of the arguments in the ''@Inject'' annotation: Now, to specify where in the targeted method we want our injection to happen, we add something that may be a bit counterintuitive to some, an ''@At'' annotation as one of the arguments in the ''@Inject'' annotation:
-<yarncode java>+<code java>
 abstract class TutorialFirstMixin { abstract class TutorialFirstMixin {
  
-    @Inject(method = "method_3735", at = @At(value = "HEAD"))+    @Inject(method = "loadLevel", at = @At(value = "HEAD"))
     private void logOnWorldLoad() {     private void logOnWorldLoad() {
     }     }
 } }
-</yarncode>+</code>
  
 Now, let's look closer at the annotations we have thus far. Think of them as holding a series of hierarchized instructions. A very simplified explanation would be: Now, let's look closer at the annotations we have thus far. Think of them as holding a series of hierarchized instructions. A very simplified explanation would be:
   * ''@Inject'' says that tells Mixin that it should inject a call to this method at specified point(s)   * ''@Inject'' says that tells Mixin that it should inject a call to this method at specified point(s)
-    *  ''method = "<yarn method_3735>"'' narrows down the point of injection to within that target method+    *  ''method = "loadLevel"'' narrows down the point of injection to within that target method
     * ''at = @At'' is used to specify what to look for in the targeted method(s)     * ''at = @At'' is used to specify what to look for in the targeted method(s)
       * ''value = "HEAD"'' specifies that Mixin should try to inject at the very start of the method as the point of injection.       * ''value = "HEAD"'' specifies that Mixin should try to inject at the very start of the method as the point of injection.
Line 95: Line 95:
  
 If we fix the signature and add a call to our mod's logger, the full class becomes: If we fix the signature and add a call to our mod's logger, the full class becomes:
-<yarncode java>+<code java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
 abstract class TutorialFirstMixin { abstract class TutorialFirstMixin {
  
-    @Inject(method = "method_3735", at = @At(value = "HEAD"))+    @Inject(method = "loadLevel", at = @At(value = "HEAD"))
     private void logOnWorldLoad(CallbackInfo ci) {     private void logOnWorldLoad(CallbackInfo ci) {
-        ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!")+        ExampleMod.LOGGER.info("MinecraftServer$loadLevel has started!");
     }     }
 } }
-</yarncode>+</code>
  
 And that's it for making this Mixin! And that's it for making this Mixin!
Line 114: Line 114:
 ==== Extending the target class's parents ==== ==== Extending the target class's parents ====
 When the class you're targeting extends and/or implements parents, mimicking that on your Mixin class is an overall benefit, as it allows you to get all of the parent methods directly. For our example Mixin it'll look like: When the class you're targeting extends and/or implements parents, mimicking that on your Mixin class is an overall benefit, as it allows you to get all of the parent methods directly. For our example Mixin it'll look like:
-<yarncode java> +<code java> 
-abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServerChunkErrorHandlerclass_2165, AutoCloseable {+abstract class TutorialFirstMixin extends ReentrantBlockableEventLoop<TickTask> implements ServerInfoChunkIOErrorReporterCommandSource, AutoCloseable {
     public TutorialFirstMixin(String string) {     public TutorialFirstMixin(String string) {
         super(string)         super(string)
     }     }
  
-    @Inject(method = "method_3735", at = @At(value = "HEAD"))+    @Inject(method = "loadLevel", at = @At(value = "HEAD"))
     private void logOnWorldLoad(CallbackInfo ci) {     private void logOnWorldLoad(CallbackInfo ci) {
-        ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!");+        ExampleMod.LOGGER.info("MinecraftServer$loadLevel has started!");
     }     }
 } }
-</yarncode>+</code>
  
 The constructor does not impact functionality, it's only there for the sake of extending without errors. Notice that we didn't implement any methods from the supers, this is thanks to our class being abstract. Abstract classes do not need to implement methods from parents, allowing us to not have to handle all the overrides you would normally get. The constructor does not impact functionality, it's only there for the sake of extending without errors. Notice that we didn't implement any methods from the supers, this is thanks to our class being abstract. Abstract classes do not need to implement methods from parents, allowing us to not have to handle all the overrides you would normally get.
Line 133: Line 133:
 === Exporting === === Exporting ===
 Mixin provides debugging utilities, one of them is the ability to export the transformed classes once they're loaded. This allows to be able to specifically check what our Mixin has targeted and if it has applied where we wanted it to.\\ Mixin provides debugging utilities, one of them is the ability to export the transformed classes once they're loaded. This allows to be able to specifically check what our Mixin has targeted and if it has applied where we wanted it to.\\
-This section's information is detailed upon more specifically and quicker at the [[tutorial:mixin_export|Exporting Mixin Classes]] page.+This section's information is detailed upon more specifically at the [[tutorial:mixin_export|Exporting Mixin Classes]] page.
  
 Exporting our Mixin would look like: Exporting our Mixin would look like:
-<yarncode java>+<code java>
 @Debug(export = true) @Debug(export = true)
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
-abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServerChunkErrorHandlerclass_2165, AutoCloseable {+abstract class TutorialFirstMixin extends ReentrantBlockableEventLoop<TickTask> implements ServerInfoChunkIOErrorReporterCommandSource, AutoCloseable { 
 +    public TutorialFirstMixin(String string) { 
 +        super(string) 
 +    }
  
-    @Inject(method = "method_3735", at = @At(value = "HEAD"))+    @Inject(method = "loadLevel", at = @At(value = "HEAD"))
     private void logOnWorldLoad(CallbackInfo ci) {     private void logOnWorldLoad(CallbackInfo ci) {
-        ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!");+        ExampleMod.LOGGER.info("MinecraftServer$loadLevel has started!");
     }     }
 } }
-</yarncode>+</code>
 Setting a Mixin to be exported makes it so that once the targeted class has been transformed and has loaded (which could be anytime between the client starting and a world loading) Mixin will export a version of that now transformed class. It should be exported to the directory ''run/.mixin.out'', and can be read using a decompiler or IDE equipped with one. You can also configure your development runs to export //all// Mixins by adding the following argument to your VM options: ''-Dmixin.debug.export=true'' Setting a Mixin to be exported makes it so that once the targeted class has been transformed and has loaded (which could be anytime between the client starting and a world loading) Mixin will export a version of that now transformed class. It should be exported to the directory ''run/.mixin.out'', and can be read using a decompiler or IDE equipped with one. You can also configure your development runs to export //all// Mixins by adding the following argument to your VM options: ''-Dmixin.debug.export=true''
  
tutorial/mixin_your_first_mixin.1767114976.txt.gz · Last modified: 2025/12/30 17:16 by gauntrecluse