GT 配方表遍历
getGregtechRecipes() 是 RecEx 里最大的一块逻辑:它把 GregTech API 生态下所有公开 RecipeMap 的配方全部抓出来,并按机器分组后写成 type: "gregtech" 那一段。
遍历的 5 个 RecipeMap 容器类
类被硬编码在 Arrays.asList(...) 里:
| 顺序 | 类 |
|---|---|
| 1 | gregtech.api.recipe.RecipeMaps |
| 2 | gtPlusPlus.api.recipe.GTPPRecipeMaps |
| 3 | bartworks.API.recipe.BartWorksRecipeMaps |
| 4 | goodgenerator.api.recipe.GoodGeneratorRecipeMaps |
| 5 | tectech.recipe.TecTechRecipeMaps |
即 GregTech 5U 本体 + GT++ + BartWorks + Good Generator + TecTech。
反射取 map 的方式
for (Class<?> recipeMapClass : recipeMapClasses) {
for (Field field : recipeMapClass.getDeclaredFields()) {
if (field.getType() == RecipeMap.class) {
maps.add((RecipeMap<RecipeMapBackend>) field.get(null));
}
}
}
三个特点:
- 用
getDeclaredFields()——只扫这些类自身声明的字段,不扫父类或接口继承来的 - 筛选条件是
field.getType() == RecipeMap.class的精确裸类型相等。泛型参数(RecipeMap<Fluid>之类)不影响判定,因为 Java 泛型擦除后getType()就是RecipeMap;但若某 map 字段被声明成某个RecipeMap的子类型或别名类型,就会被漏掉 field.get(null)直接取静态字段值;失败(IllegalArgumentException/IllegalAccessException)只printStackTrace()后继续下一个字段,不中断导出
⚠️ 这 5 个类都是编译期硬依赖。RecipeExporter.java 顶部直接 import 它们的全限定名(bartworks.API.recipe.BartWorksRecipeMaps 等),没有 Loader.isModLoaded 之类的可选加载判断——任何一个 mod 缺失,RecipeExporter 类在加载时就会 NoClassDefFoundError,run() 整体失败。也就是说 RecEx 实际上无法单独运行,必须装齐 GT5U 及其这几个附属。
dependencies.gradle 里只显式声明了 GT5-Unofficial,这 5 个附属随 GT5U 一同提供。
分组规则
每个 map 生成一个 GregtechMachine 对象:
| 字段 | 类型 | 内容 |
|---|---|---|
n |
String |
机器名 |
recs |
List<GregtechRecipe> |
该 map 的全部配方 |
n 的取值:StatCollector.translateToLocal(map.unlocalizedName),若为 null 或空串则回退到 map.unlocalizedName 原值。
分组是为压缩体积服务的。RecipeExporter 的 Javadoc 解释:
Unlike vanilla recipes, the current schema here groups recipes from each machine together. This is a minor file size improvement. Rather than specifying the machine’s name in every recipe, the machine name is only listed once for the entire file.
即:机器名只写一次,而不是每条配方重复一次。同一段注释还说明这种格式不阻碍 NEP(NotEnoughProduction)加载。
每个 map 处理时打两条 INFO 日志:"Processing recipe map " + 机器名 与 "Finished sorting recipes for map " + 机器名。
配方内容的转换
每条 GTRecipe 先经 cloneAndSort 深拷贝并排序(COMPARE_RECIPE),再逐字段搬进 GregtechRecipe:
| GTRecipe 字段 | 导出字段 | 处理 |
|---|---|---|
mEnabled |
en |
直接赋值 |
mDuration |
dur |
直接赋值 |
mEUt |
eut |
直接赋值 |
mSpecialValue |
sp |
!= 0 时赋值(但 Gson 仍总会写出该键) |
mInputs |
iI |
clean() 去 null + 排序 + 逐个 formatGregtechItemStack |
mOutputs |
iO |
同上 |
mFluidInputs |
fI |
clean() 去 null + 排序 + formatGregtechFluidStack |
mFluidOutputs |
fO |
同上 |
与 GT 顺序分支不同,这里有 if (item == null) continue; 守卫(clean() 已提前剔除 null,实际是双保险)。
⚠️ 被复制但未被导出的 GT 字段:mSpecialItems、mInputChances、mNeedsEmptyOutput、isNBTSensitive、mCanBeBuffered、mFakeRecipe、mHidden。其中 mHidden / mFakeRecipe 的后果最实际——隐藏配方和伪配方会被正常导出且不带任何标记,消费者只能靠 en(对应 mEnabled)过滤。详见 导出 JSON 格式。
cloneAndSort 顺带把 mInputs/mOutputs/mFluidInputs/mFluidOutputs 换成 clean() 后的紧凑数组,所以导出文件里 GT 配方的材料列表已排序且无空位——与 shaped 保留空槽的做法不同(见 配方类型对比)。
相关条目
- 导出 JSON 格式 -
gregtech段的字段定义 - 配方类型对比 - GT 之外 4 类配方为何另起一套包装类
- 成分类型 -
iI/iO与fI/fO的元素类型差异