配方类型对比
RecEx 用 4 个自有的数据包装类承载 4 类配方,字段名与输出 JSON 一一对应。它们相对原版/Forge 配方的共同改动是:把 ItemStack 之类的对象图拍平成只有基本类型与 String 的浅结构,以便 Gson 直接序列化。
对比表
| 维度 | ShapedRecipe |
ShapelessRecipe |
OreDictShapedRecipe |
FurnaceRecipe |
|---|---|---|---|---|
| 源类型 | net.minecraft.item.crafting.ShapedRecipes |
net.minecraft.item.crafting.ShapelessRecipes |
net.minecraftforge.oredict.ShapedOreRecipe |
FurnaceRecipes.smelting().getSmeltingList() 的 Map 条目 |
JSON type |
shaped |
shapeless |
shapedOreDict |
smelting |
| 输入字段 | iI |
iI |
iI |
input |
| 输出字段 | o |
o |
o |
output |
| 输入容器 | List<Item> |
Item[] |
List<IItem> |
单个 Item |
| 输入元素类型 | 恒为 Item |
恒为 Item |
Item 或 ItemOreDict |
Item |
| 保留空槽 | 是(null 占位) |
不适用(本来就没有空格) | 不适用 | 不适用 |
| 类形态 | 普通类 | 普通类 | 普通类 | Java record(@Desugar) |
| 抓取方式 | 遍历 recipeItems 网格数组 |
遍历 recipeItems 列表 |
6 分支类型判定 | Map entrySet().stream() |
| 额外逻辑 | 无 | 输入排序两次 | 类型分派 + OreDict 反查 | 无 |
各类型多抓了什么
ShapedRecipe — 有序合成
for (ItemStack stack : original.recipeItems) {
Item item = RecipeUtil.formatRegularItemStack(stack);
rec.iI.add(item); // ← 没有 null 判断
}
formatRegularItemStack(null) 返回 null,而这里没有像 GT 分支那样的 if (item == null) continue;,所以空格子会以 null 元素进入 iI。Gson 序列化 null 数组元素会写成 null,于是网格位置被保留,形状隐式编码在数组顺序里。
代价:iI 里混有 null,消费方必须容忍;同时 recipeWidth / recipeHeight 从未被读取——源码全文没有访问这两个字段,所以输出里没有宽高,只有位置。
ShapelessRecipe — 无序合成
输入容器是 Item[] 而非 List<Item>,且 iI 用 rec.iI = ...toArray(Item[]::new) 整体赋值(其它三个类都是逐个 add)。
因为无序配方没有格子概念,排序被用了两次:一次在重建 ShapelessRecipes 时对 r.recipeItems 排序,一次在映射成 Item[] 时再排一次。原版 recipeItems 本身不含 null,所以不存在空槽问题。
OreDictShapedRecipe — 矿物词典有序合成
输入是 List<IItem>——IItem 是个空标记接口(IItem.java 里只有一个 public interface IItem {}),由 Item 和 ItemOreDict 同时实现。声明成 List<IItem> 而非 List<Object> 是为了让集合语义明确。
抓取时对每个 getInput() 元素做6 分支类型判定:
| 输入的 Java 类型 | 处理 | 产出成分 |
|---|---|---|
ItemStack |
直接格式化 | Item |
String |
parseOreDictionary(String) 反查 OreDict |
ItemOreDict |
String[] |
parseOreDictionary(String[]) 合并多名字 |
ItemOreDict |
net.minecraft.item.Item |
包成 new ItemStack(item) |
Item |
Block |
包成 new ItemStack(block, 1, Short.MAX_VALUE) |
Item(m = 32767) |
ArrayList<?> |
逐个元素 formatRegularItemStack,并把元素命中的 OreDict 名收进 dns |
ItemOreDict |
| 其它 | log.warn("OreDict Input Type not parsed! ...") |
不产出,被静默丢弃 |
因此 iI 数组里可能混着两种形状完全不同的对象(Item 与 ItemOreDict),Gson 按运行时类型序列化,消费方可以靠键是否存在(有 dns/ims 即为 ItemOreDict)区分。
ArrayList 分支的细节:
- 每个元素调
OreDictionary.getOreIDs(stack),把得到的名字收进dns - 过滤掉
null、空串、以及equalsIgnoreCase("Unknown")的名字 - 去重也用
equalsIgnoreCase - 入口有
if (!list.isEmpty())守卫,且内联构造ItemOreDict后还会判if (!item.ims.isEmpty()),空结果不产出
String[] 分支不内联这道守卫,而是委托给
RecipeUtil.parseOreDictionary(String[])。该重载只守了数组长度:
if (names == null || names.length == 0) return null;
...
return retItem; // 无条件返回,未检查合并后的 dns/ims 是否为空
所以全部名字都查不到时,它仍返回一个非 null 但 dns/ims 为空的
ItemOreDict;调用处只判 if (item != null),空壳于是被写进 iI。
(单名重载 parseOreDictionary(String) 确实有 items.isEmpty() 守卫并返回 null,
所以单名分支不会出空壳。)不对称就在这里。
细节与完整代码见 成分类型对比 中的专节。
排序方面,OredictInput 为 6 种可能的输入类型各配了一个比较器(ItemStack / String / String[] / Item / List / ItemStack[]),COMPARE_OREDICT_INPUT 先比较"类型序号"再按对应比较器比。
FurnaceRecipe — 熔炼
public record FurnaceRecipe(Item input, Item output) {}
唯一使用 record 语法的类(配 @Desugar 以发布 Java 8 字节码)。它是全 mod 唯一字段名未被缩写的类型:JSON 键是 input / output 而非 iI / o。
数据来源是 FurnaceRecipes.smelting().getSmeltingList() 这个 Map<ItemStack, ItemStack>,按 entrySet() 流式映射,没有排序——导出顺序取决于原版 Map 的迭代顺序。熔炼配方没有"有序"概念,所以不存在形状丢失问题。
差异归因
4 个类型的差别本质上来自它们各自源类型的差别:
Shaped/Shapeless分离,是因为原版就是两个不同的类,网格语义不同OreDictShaped单独一个,是因为 Forge 的ShapedOreRecipe输入是异构多态的(6 种类型),普通Item装不下Furnace单独一个,是因为它根本不是"合成"而是"输入→输出"的映射,没有输入集合- GT 配方连包装类体系都没加入这 4 个,而是另起
GregtechMachine/GregtechRecipe一套(按机器分组,含eut/dur等 GT 专有量),见 GT 配方表遍历
相关条目
- 导出 JSON 格式 - 这些类对应的
type与字段名 - 成分类型 -
iI数组里装的是什么