成分类型
导出文件里的每一个"料"都是 3 个数据类之一,外加 1 个空标记接口。它们是整份 JSON 的最小单元。
枚举
| 类 | 包 | 是否实现 IItem |
用途 |
|---|---|---|---|
IItem |
recipes.ingredients |
— | 空标记接口 |
Item |
recipes.ingredients |
是 | 普通物品成分 |
ItemOreDict |
recipes.ingredients |
是 | 矿物词典成分 |
Fluid |
recipes.ingredients |
否 | 流体成分 |
IItem.java 全文只有一个方法体为空的 public interface IItem {}。它的 Javadoc 说明了存在的唯一理由——让 List<IItem> 能同时装 Item 和 ItemOreDict 而不必用 List<Object>。Fluid 不实现它,因为流体永远不会出现在同一个数组里。
Item — 物品成分
| 字段 | 类型 | 含义 | 赋值来源 |
|---|---|---|---|
a |
int |
数量 | stack.stackSize |
m |
int |
伤害值 / metadata | stack.getItemDamage() |
id |
String |
唯一标识 | GameRegistry.findUniqueIdentifierFor(stack.getItem()).toString() |
lN |
String |
本地化名 | stack.getDisplayName() |
nbt |
String |
NBT 标签(NBTTagCompound.toString()) |
stack.hasTagCompound() ? ... : null |
id 的回退链:findUniqueIdentifierFor 返回 null 时改用 stack.getUnlocalizedName();整个取值包在 try/catch (Exception e) {} 里,异常被静默吞掉,id 就会留 null。lN 同样包在空 catch 里。
两个格式化方法的唯一区别是 lN 的回退链:
| 方法 | lN 取值顺序 |
|---|---|
formatRegularItemStack |
stack.getDisplayName(),失败即留 null |
formatGregtechItemStack |
stack.getDisplayName() → 失败则 GTLanguageManager.getTranslation(stack.getUnlocalizedName()) → 再失败留 null |
普通合成 / 无序合成 / 矿物词典 / 熔炼用前者,GT 配方用后者。id / m / a / nbt 四个字段两者完全一致。
NBT 的表达方式
nbt 是 String 引用类型,无 NBT 时为 null。Gson 默认配置不序列化 null 字段,所以:
{"a":1,"m":0,"id":"gregtech:gt_ingot_iron","lN":"Iron Ingot"}
{"a":1,"m":0,"id":"minecraft:diamond_sword","lN":"Diamond Sword","nbt":"{display:{Name:\"Excalibur\"}}"}
nbt 键的存在与否就是"有无 NBT"的标志——不是 "nbt": null,而是整个键缺席。
NBT 以 NBTTagCompound.toString() 的形式写入,是 SNBT 文本而非二进制。GT 流体的 NBT 则完全不导出(见下)。
ItemOreDict — 矿物词典成分
| 字段 | 类型 | 含义 |
|---|---|---|
dns |
List<String> |
词典名(dictionary name(s)) |
ims |
List<Item> |
匹配该词典名的物品 |
与 Item 的根本差异:它不描述一个具体物品,而是描述一个"任意一个满足词典的物品",内部 ims 才是具体 Item 列表。构造时在构造函数里初始化两个 ArrayList(Gson 序列化时会直接读已填充的字段)。
两条产生路径:
parseOreDictionary(String name)→dns只放这 1 个名字;名字为null/空串、或OreDictionary.getOres(name)返回空时返回nullparseOreDictionary(String[] names)→ 逐个调用上面的方法并addAll合并;数组为null/空时返回null,但合并结果为空时仍返回一个非 null、dns/ims皆空的对象(见下方详述)ArrayList<?>输入分支(见 配方类型对比)不走parseOreDictionary,而是直接把元素格式化后ims.add,并自行收集dns
因此输出里 iI 数组可能出现 {"dns":[],"ims":[]} 这样的空壳,原因见下。
String[] 重载为什么能产出空壳
单名重载有两道守卫:
public static ItemOreDict parseOreDictionary(String name) {
if (name == null || name.isEmpty()) return null;
List<Item> items = searchOreDictionary(name);
if (items == null || items.isEmpty()) return null; // ← 查不到就 null
...
}
但数组重载只守了长度,没守合并结果:
public static ItemOreDict parseOreDictionary(String[] names) {
if (names == null || names.length == 0) return null; // ← 只守这个
ItemOreDict retItem = new ItemOreDict();
for (String name : names) {
ItemOreDict tmpItem = parseOreDictionary(name);
if (tmpItem != null) { retItem.dns.addAll(...); retItem.ims.addAll(...); }
}
return retItem; // ← 无条件返回,不检查 dns/ims 是否为空
}
所以当每个名字都查不到时,循环一次都没进去,仍原样返回那个空的 retItem。
调用处(RecipeExporter)只判 if (item != null),于是空壳被写进 iI。
对比之下 ArrayList 输入分支在调用处内联了 if (!list.isEmpty()) 与
if (!item.ims.isEmpty()) 两道守卫,所以那条路径不会产出空壳——不对称就在这里。
Fluid — 流体成分
| 字段 | 类型 | 含义 | 赋值来源 |
|---|---|---|---|
a |
int |
数量 | stack.amount |
id |
String |
唯一标识 | stack.getFluid().getName()(Forge 流体名) |
lN |
String |
本地化名 | 三级回退,见下 |
lN 有三级回退(formatGregtechFluidStack):
GTLanguageManager.getTranslation(stack.getUnlocalizedName())- 失败 →
stack.getFluid().getName() - 再失败 →
stack.getLocalizedName()
每一级各自包在独立的 try/catch 里。
⚠️ Fluid 没有任何 NBT 字段。FluidStack 其实带 tag,COMPARE_FLUID_STACKS 排序器也读了 s.tag(注释:“no one uses fluid stack nbt”),但格式化时并未把它写入输出——所以带 NBT 的流体与不带 NBT 的流体导出结果完全相同。这是 Fluid 与 Item 的一处实质不对称。
Fluid 只出现在 GT 配方的 fI / fO 里,其余 4 类配方都是纯物品。
相关条目
- 配方类型对比 - 哪些数组里会装这些成分
- 导出 JSON 格式 - Gson 序列化规则如何影响
nbt键