列舉擴充
列舉擴充是 Mixin 的一項功能,可可靠地向列舉新增條目。
當目標是 Minecraft 列舉時,你可以將 Mixin 與類別調整搭配使用,讓新的列舉條目顯示在反編譯原始碼中。 如果將其設為傳遞性,依賴你的模組的其他模組也會看到你新增的條目。
WARNING
列舉擴充需要至少 Loader 0.19.0 才能取得 Mixin 支援,並且需要至少 Loom 1.16 才能取得類別調整器支援。
此外,類別調整器檔案標頭必須指定 v2 作為版本,才能使用列舉擴充。
建立 Mixin
在建立 Mixin 類別之前,請先確認你的 fabric.mod.json 檔案中,已明確將 Loader 0.19.0 或以上版本列為相依項目:
json
...
"depends": {
...
"fabricloader": ">=0.19.0"
...
}
...即使你已在 Gradle 相依項目中使用正確的 Loader 版本,也必須明確依賴至少 0.19.0 版本,才能選擇啟用這項 Mixin 功能。
若要建立列舉擴充,請在你的 Mixin 套件中建立一個 enum,以 @Mixin 標註它,然後像是把常數加入目標列舉類別本身一樣,將你的常數加進去。 例如,讓我們為 RecipeBookType 新增一個條目:
java
@Mixin(RecipeBookType.class)
enum RecipeBookTypeMixin {
EXAMPLE_MOD_RECIPE_BOOK_TYPE
}1
2
3
4
2
3
4
重要
你應始終使用你的模組 ID 作為所新增列舉常數的前綴,以確保唯一性。 在這些文件中,我們會使用 EXAMPLE_MOD_。
傳遞建構子引數
如果目標列舉沒有預設建構子,你必須 shadow 目標類別中的建構子,並在新增條目的宣告中傳入所需引數。
例如,讓我們新增一個 RecipeCategory 條目。 建立一個與目標類別中所需建構子相符的建構子,並以 @Shadow 標註它。
java
@Mixin(RecipeCategory.class)
enum RecipeCategoryMixin {
EXAMPLE_MOD_RECIPE_CATEGORY("exampleModRecipeFolderName");
@Shadow
RecipeCategoryMixin(String recipeFolderName) {
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
實作抽象方法
若要實作目標列舉的抽象方法,請 shadow 該抽象方法,然後在你新增的條目中覆寫並實作它。 例如,讓我們新增一個 ConversionType 條目:
java
@Mixin(ConversionType.class)
enum ConversionTypeMixin {
EXAMPLE_MOD_CONVERSION_TYPE(false) {
@Override
void convert(Mob from, Mob to, ConversionParams params) {
/* ... */
}
};
@Shadow
abstract void convert(Mob from, Mob to, ConversionParams params);
@Shadow
ConversionTypeMixin(final boolean discardAfterConversion) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
建立類別調整器條目
如果你的目標是 Minecraft 列舉,可以使用類別調整器條目,在反編譯原始碼中明確修改目標列舉。
若要選擇啟用此功能,請記得使用 Loom 1.16 或以上版本,並將檔案標頭版本設為 v2。
列舉擴充條目的語法如下:
extend-enum <targetClassName> <ENUM_CONSTANT_NAME>對於類別調整,類別會使用其內部名稱。
例如,我們在 Mixin 章節中新增的 RecipeBookType 常數,其類別調整器條目會是:
classtweaker
extend-enum net/minecraft/world/inventory/RecipeBookType EXAMPLE_MOD_RECIPE_BOOK_TYPE套用變更
你必須重新整理 Gradle 專案並重新產生原始碼,才能在反編譯原始碼中看到新增的列舉條目。 若修改沒有出現,可以嘗試驗證檔案,並檢查是否出現任何錯誤。
現在你可以在程式碼中使用該列舉常數:
java
void exampleRecipeBookTypeMethod(RecipeBookType recipeBookType) {
if (recipeBookType == RecipeBookType.EXAMPLE_MOD_RECIPE_BOOK_TYPE) {
/* ... */
}
}1
2
3
4
5
2
3
4
5
如果你只透過 Mixin 新增它,且它不在反編譯原始碼中,可以透過比較名稱來檢查它:
java
void exampleRecipeBookTypeMethodNoCT(RecipeBookType recipeBookType) {
if (recipeBookType.name().equals("EXAMPLE_MOD_RECIPE_BOOK_TYPE")) {
/* ... */
}
}1
2
3
4
5
2
3
4
5
如果你需要在多個地方使用該常數,請呼叫 valueOf 取得它,並將結果儲存在欄位中:
java
public static final RecipeBookType ADDED_RECIPE_BOOK_TYPE = RecipeBookType.valueOf("EXAMPLE_MOD_RECIPE_BOOK_TYPE");1
注意事項
列舉擴充無法保證你新增的條目不會破壞任何東西。
你有責任檢查目標列舉的使用情況,並盡可能預防問題。 如果某些問題無法解決且導致崩潰,最好完全不要使用列舉擴充。
本節會說明擴充列舉時需要注意並避免的一些模式,但並非完整清單。
Switch 運算式
Switch 陳述式常用於處理列舉常數。 因此,如果某個 switch 運算式沒有處理由其他模組新增的條目,就可能發生崩潰。 例如,假設我們有以下 switch 運算式:
java
void exampleProblematicSwitch(RecipeBookType recipeBookType) {
String s = switch (recipeBookType) {
case SMOKER -> "smoker";
case CRAFTING -> "crafting";
case FURNACE -> "furnace";
case BLAST_FURNACE -> "blast_furnace";
case EXAMPLE_MOD_RECIPE_BOOK_TYPE -> "example_mod_recipe_book_type";
};
/* ... */
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
請注意,這裡沒有 default 子句。 即使我們已經處理了原版列舉中的所有值,以及我們自己新增的值,如果另一個模組新增了不同條目,這裡仍會擲出例外。
要如何避免這種情況? 沒有單一通用的方法可以避免崩潰——你的做法應視具體情況調整。 不過,一般而言:
- 如果
switch運算式位於原版方法中,你可以使用 Mixin 修改它。 - 如果
switch運算式來自某個模組,你應嘗試聯絡開發者,一起制定相容的做法。 否則,你可能需要對另一個模組建立 Mixin。
序列化列舉
某些列舉的條目會自動被序列化。 例如 Axolotl 類別中的 Variants 列舉。
擴充這些列舉會在 Minecraft 的命名空間下序列化你的自訂條目;在某些版本中,這甚至可能基於數值 ID 發生。 這並不理想,因為它可能影響所有其他條目的索引。
如果列舉條目會以這種方式序列化,最好完全避免擴充該列舉。 取而代之的是,如果有可用 API,你可能應該尋找並使用它。






