frogmc.dev/exts/mixin.md
2024-06-18 00:48:36 -05:00

97 lines
6 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mixin
[Mixin](https://github.com/SpongePowered/Mixin) is an ASM framework to modify the code of the game created by the SpongePowered project. Mixin is widely supported by many modloaders, and frog is no exception, offering support for using Mixin in mods. This document is not intended as a comprehensive tutorial on the specifics of Mixin, but it is designed to give a basic understanding of its principles as well as how to use it in a frog mod.
## Adding Mixins
A Mixin is a specially-annotated class that goes in a package within your mod. Mixins, by default, are common to both the server and client; however, it is possible to write a server or client-specific mixin. By convention, this package is called `mixin` and is in the root package of your mod. Additionally, a metadata file called `<modid>.mixins.json` (with `<modid>` replaced with your mod's namespace id) should be located in your `resources` folder. It should be in JSON format, and should have the following fields:
- `required`: internal use; should be set to `true`.
- `minVersion`: the minimum supported version of Mixin. Should be set to `"0.8".
- `package`: the package that contains your mixins.
- `compatibilityLevel`: the version of java your mixins should be compatible with. Should be formatted as `JAVA_` followed by the major version, i.e. `JAVA_17`, `JAVA_21`
- `mixins`: an array of strings containing the names of mixins that will apply on both the client and server. Should be just the names of mixin classes.
- `client`: an array of strings containing the names of mixins that will apply on just the client. Should be just the names of mixin classes.
- `server`: an array of strings containing the names of mixins that will apply on just the server. Should be just the names of mixin classes.
- `injectors`: internal use; should be set to `{ "defaultRequire": 1 }`
The following is an example of a .mixins.json file containing no mixins:
```json
{
"required": true,
"minVersion": "0.8",
"package": "com.example.examplemod.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [],
"client": [],
"server": [],
"injectors": {
"defaultRequire": 1
}
}
```
For the mixins to apply, this file must be included in the `[frog.extensions]` table in your `frog.mod.toml` file, as follows:
```toml
[frog.extensions]
...
mixin = "<modid>.mixins.json"
...
```
Replace `<modid>` with your mod's namespace id, and you're ready to write mixins!
## Mixin Example
A Mixin represents a hook to modify a single class of Minecraft code. The mixin takes the form of a class with an annotation. For this example, we will create a Mixin class that will modify the title screen. Begin by creating a Mixin class in the `mixin` package. Although other naming schemes are used, typically it's not a bad idea to name your file the name of the package you're modifying followed by the word `Mixin`. So, to modify the `TitleScreen` class, we'll create a class called `TitleScreenMixin`. Create this file, and add the `@Mixin` annotation to specify what class we'll be modifying.
```java
package com.example.examplemod.mixin;
import net.minecraft.client.gui.screens.TitleScreen;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(TitleScreen.class)
public class TitleScreenMixin {
}
```
We have to add this to our previously-created `<modid>.mixins.json` file for the loader to recognize it. Since we will be modifying the title screen, we will put the class name of our mixin in the `client` array instead of the `mixins` array:
```json
client: ["TitleScreenMixin"]
```
With this added to our Mixin configuration it should now attempt to load, however there are still errors in our mixin. This is because the signature of the mixin class must match that of the class that is being modified. `TitleScreen` extends the `Screen` class, so our mixin must also extend `Screen`. To not have to implement all of the classes within `Screen`, we can mark the class abstract.
```java
package com.example.examplemod.mixin;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(TitleScreen.class)
public abstract class TitleScreenMixin extends Screen {
protected TitleScreenMixin(Component title) {
super(title);
}
}
```
Now, we have a mixin that will do nothing, however it is set up to modify the `TitleScreen` class. For this tutorial we will use one of the simplest Mixin types: the **Injector**. An injector adds code into an existing method within the target function, as if the code were copied and pasted from your mixin class to the game's code. In this case, we will inject into the method that adds menu elements to the title screen, called `createNormalMenuOptions`. We want this element to be added last, or at the tail of the method. With this in mind, we can write our mixin function header, within the mixin class:
```java
...
@Inject(method = "createNormalMenuOptions", at = @At("TAIL"))
private void showExample(int y, int rowHeight, CallbackInfo ci) {
...
}
```
Note that the arguments of the method are the same as the arguments of the method we're going to inject into, with the addition of a parameter of type `CallbackInfo`. The `CallbackInfo` parameter provides information about the method to the mixin. Keep in mind that returning in the mixin will not return the target method, but will just return to the existing code of that method. For this example, we'll add a text widget to the title screen, just to show us that the game has been modified.
```java
...
private void showExample(int y, int rowHeight, CallbackInfo ci) {
var widget = new FocusableTextWidget(200, Component.literal("<insert frog here!>"), this.font);
widget.setPosition(width / 2 - widget.getWidth(), 20);
addRenderableOnly(widget);
}
```
Now launch the game, and if everything worked properly you should see the text "<insert frog here!>" on the title screen!
For more advanced information about the Mixin system, see [the Mixin wiki](https://github.com/SpongePowered/Mixin/wiki).