# Minecraft Fabric Modding - Mixin
Mixins 是 Fabric 生態系統中使用的強大而重要的工具。他可以用來修改遊戲現有代碼的技術,無論是通過注入自定義邏輯、刪除機制或是修改值。
## Mixin
首先,選定一個想要 Mixin 的 class,此以 `PlayerEntity` 做舉例。
首先要在 `resources` 路徑下的 `<modid>.mixins.json` 寫下:
```json=
{
"required": true,
"minVersion": "0.8",
"package": "<mixins path>",
"compatibilityLevel": "JAVA_17",
"mixins": [
// mixins
"PlayerEntityMixin"
],
"client": [],
"injectors": {
"defaultRequire": 1
}
}
```
`<mixins path>` 就是你要放 Java class 的地方。
`"mixins"` 裡面就是放 Java class 的名子(任意)。
Java class 的 Mixin 很簡單,只要在函數上面加一個 `@Mixin`,裡面放入 `<Class>.class` 就可以了
```java=
@Mixin(PlayerEntity.class)
public class PlayerEntityMixin {
}
```
## Injects - 注入
Injects 是 Mixin 中的重要技術,讓你可以在現有代碼間加入程式碼。
函數的標準書寫法:
```java=
@Inject(method = "<METHOD>", at = @At("<AT>"), cancellable = true)
private void injectMethod(<ARGS>, CallbackInfo ci) {
}
```
`<ARGS>` 是 `<METHOD>` 的參數;`CallbackInfo` 是用在函數回傳 `void` 時,若是函數有回傳值,就須使用 `CallbackInfoReturnable<T>` 代替,`T` 為回傳值型態。
### method
method 要放入的參數是目標函數的「位元組碼(Bytecode)」。如果你是使用 IntelliJ 撰寫程式的話,可以點開 View/Show Bytecode (快速鍵預設是 None)

Bytecode 就是給 Java 虛擬機執行的一個指令格式。簡單來說,就是表示「這個函數長怎樣」的一個通用規則。以下是一些基本型態的 Bytecode:
| 型態 | Bytecode |
| ------- | -------- |
| byte | B |
| char | C |
| double | D |
| float | F |
| int | I |
| long | J |
| short | S |
| boolean | Z |
如果是陣列的話就會在前面加上 `[`;若是其他 class 的話,則需以 `L<ClassName>;` 表示,例如 `String[]` 型態就需以 `[Ljava.lang.String;` 表示,特別注意 `<ClassName>` 需使用**全名**。
函數的 Bytecode 表示法就是 `funcName(<args>)<return>`,像是
```java
protected boolean isIn(ItemGroup group)
```
就可以寫成
```
protected isIn(Lnet/minecraft/item/ItemGroup;)Z
```
寫入 method 中時,把前面的存取修飾子省略。
事實上,在 inject 的 method 中,我們可以直接填入函數的名稱即可。
### at
at 中的 `@At` 技術就稍微有點複雜,這邊先講較為基礎的單參數形式。
`@At("")` 中放的是「插入點參考位置」,分別有 `HEAD`、`RETURN`、`INVOKE`、`TAIL` 這四種,其中 `INVOKE` 需要搭配 `@At` 中的 `target` 使用,到後面會再提及。
| Ref | 意義 |
| ------ | ------------------------ |
| HEAD | 在執行函數內部程式碼之前 |
| RETURN | 在**每一次**回傳時都執行一次 |
| TAIL | 在**最後一次**回傳時執行 |
### cancellable
就是在呼叫 `CallbackInfo#cancel` 或者 `CallbackInfoReturnable<T>#setReturnValue(T)` 時是否直接停止函數的運行。
### constructor - 建構子
觀察建構子的 bytecode,你會發現其為 `<init>(...)V`,放入 method 中,其餘與上面相同。
### static
如果函數是 static 的,一樣可以進行 mixin,但函數必需改成 `private static` 型態。
## Accessor
## Invoker
## Shadow
## 共同父類
有時候在實作 Mixin 時,會用到目標 class 的 **父 class** 中的函數,此時我們可以將 Mixin class 直接去 extend 目標 class 的 **父 class**。以 `PlayerEntity` 為例,其 父 class 為 `LivingEntity`,實際寫起來就會像這樣:
```java=
@Mixin(PlayerEntity.class)
public class PlayerEntityMixin extends LivingEntity {
protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
}
...
}
```
這樣如果要使用 `LivingEntity` 內的函數時,就可以直接用 `this.<func>` 呼叫
:::warning
若是 父 class 為 abstract,則直接將 constructor extend 即可,無須更改任何東西。
:::
## Interface
有時候,我們會想在目標 class 中加入一些變數,但是直接加是不可行的,因此我們需要用到 interface 來解決此問題。
像是我撰寫一個 interface:
```java=
public interface IPlayerDataSaver {
float getWater();
}
```
然後將我們剛剛寫的 `PlayerEntityMixin` 去 implement 這個 interface
```java=
@Mixin(PlayerEntity.class)
public class PlayerEntityMixin extends LivingEntity implements IPlayerDataSaver {
@Override
public float getWater() {
return 0.1f;
}
}
```
當你遇到 PlayerEntity 想要 getWater 時,只需要將 PlayerEntity cast 成 IPlayerDataSaver 即可。實做:
```java
PlayerEntity player;
((IPlayerDataSaver) player).getWater(); // 0.1f
```
:::warning
你一定會想說,為甚麼需要 `IPlayerDataSaver` 這個 interface,不是直接把 `PlayerEntity` cast 成 `PlayerEntityMixin` 就好嗎? 如果你這樣做,遊戲會直接崩潰,並告訴你 `PlayerEntity` 是無法 cast 成 `PlayerEntityMixin` 的,因為他們只有共同父類。
:::
## `@At`
[Javadoc](https://jenkins.liteloader.com/view/Other/job/Mixin/javadoc/index.html)

在 `value == "INVOKE"` 時,`target` 需要填入你想要 Mixin 的那一行的 Bytecode
`shift` 就是指定在 `target` 的前面、後面、或者不指定
應用的部分在 [Modifying](#Modifying) 會用到
## Modifying
### argument - 引數
當你在目標函數中呼叫一個函數時,放入的變數稱為「引數 (argument)」。
如果想要修改放入的引數,我們所要用的是 `@ModifyArg()`:
```java=
@ModifyArg(
method = "foo()V",
at = @At(
value = "INVOKE",
target = "La/b/c/Something;doSomething(ZIII)V"
),
index = 2
)
private int injected(int x) {
...
}
```
`method` 放入目標函數的 Bytecode
`at` 中,`value` 填入 `INVOKE`,`target` 放入呼叫函數那行的 Bytecode
`index` 放入你想要修改的引數***在呼叫函數中是第幾個***
存取修飾子改成 `private`,傳入值與傳出值皆為 **欲修改引數之型態**
### parameter - 參數
### local variable - 局部變數
### constant - 常數
---
###### tags: `Minecraft Fabric Modding`