配方导出流程

RecEx 的唯一功能:把当前游戏内已注册的合成配方导出成一个 JSON 文件(并额外压一个 zip)。触发方式是热键 → GUI → 按钮,不是指令,也不是自动。

触发链

玩家按 K(默认键)              KeyBindings.binding
   ↓  InputEvent.KeyInputEvent
listeners/KeyInputEventHandler.onKeyInput
   ↓  Minecraft.displayGuiScreen(单个复用的 GuiMain 实例)
GUI 显示:全屏不透明蓝底 + 居中标题 "Recipe Exporter"
   ↓  点击按钮(GuiButton id=0,200×20,坐标 50,50,文字 "Export!")
gui/GuiMain.actionPerformed
   ├─ 聊天栏打印 "Started export"
   ├─ new Thread(...).setDaemon(true).setName("recipe export thread").start()
   └─ displayGuiScreen(null)   ← 立刻关闭 GUI,不等导出结束
   ↓  子线程内
recipes/RecipeExporter.getInst().run()
   ├─ 依次抓取 5 类配方
   ├─ Gson 序列化 → 写 .json
   └─ zip4j 压缩 → 写 .zip
   ↓  聊天栏打印 "Finished export in %.2fs"

导出在子线程中执行

GuiMain.actionPerformed 把 RecipeExporter.getInst().run() 放进一个 daemon 线程(名 recipe export thread),因此导出期间主线程不被阻塞,游戏继续可操作。

界面的 doesGuiPauseGame() 被覆写为 return false——单人游戏里导出不会暂停。

drawDefaultBackground() 被覆写为空,但 drawScreen 自己先用 Color(0x000000FF)(alpha=255 的纯蓝)铺满全屏,再以 Color(0xFFFFFFFF)(不透明白色)在 y=6 处水平居中画出标题 Recipe Exporter。所以界面是不透明蓝底,而非毛玻璃。配色工具见 graphics/Color.java(从 LibGDX Color 拷贝,0xRRGGBBAA 整数构造),绘制见 graphics/GraphicsRender.rect(Tessellator + GL_QUADS)。

GUI 只有一个按钮,按 Esc 关闭(沿用 GuiScreen 默认 keyTyped 行为;GuiMain.keyTyped 只调了 super,未加任何自定义键位)。mouseClicked 同样只调 super。

异常处理的一处不一致

子线程内的 try 只捕获 Throwable,处理方式是往聊天栏发一条 S02PacketChat:

Export threw exception: <异常 toString>

但 catch 块之后没有 return,所以即使导出抛异常,随后仍会照常打印 Finished export in %.2fs。即导出失败时玩家会先看到错误、再看到一条"Finished"计时,需以错误行为准。

序列化/写盘异常在 RecipeExporter 内部另有处理:写盘失败打日志 Recipes failed to save! 后 return(此时不压 zip);压缩失败只打 Recipe compression may have failed! 警告,json 文件仍然保留。

导出的时机决定了内容

导出是运行时即时抓取当前内存里的配方注册表,所以 init/postInit 期间被任何方式增删改的配方(包括 MineTweaker / CraftTweaker 的脚本改动)都会被一并导出。代价是必须在已加载的存档/服务器内才能导出。

抓取顺序

RecipeExporter.run() 固定按以下顺序调用五个抓取方法,顺序即下方 JSON 中 sources 数组的顺序:

顺序 调用 产出
1 getGregtechRecipes() GT 系配方,按机器分组
2 getShapedRecipes() 有序合成
3 getShapelessRecipes() 无序合成
4 getOreDictShapedRecipes() 矿物词典有序合成
5 getFurnaceRecipes() 熔炼

各类型的具体序列化形态见 配方类型对比,数据模型的字段定义见 成分类型,机器分组规则见 GT 配方表遍历。

RecipeExporter 是单例(getInst() 懒加载),无状态,每次 run() 从零重建全部数据。

确定性排序

所有输入/输出列表与配方列表在写出前都经过排序,目的是让两次导出产生可 diff 的稳定文件:

  • 物品栈按 item id → damage → stackSize → NBT 字符串 排序
  • 流体栈按 FluidRegistry id → amount → NBT 字符串 排序
  • GT 配方按 mEUt → mDuration → mSpecialValue → 输入物品 → 输入流体 → 输出物品 → 输出流体 排序
  • 有序合成按 recipeItems 数组整体比较,无序合成按输入列表比较

源码注释对后两条排序器的评价是 Really bad, but idc about performance here because this will be used very rarely if ever。

排序不改变配方语义:它把 GT 配方的输入/输出数组里的 null 去掉并重新排序(clean(),会 copy() 每个栈),但对无序/有序合成是稳定排序,不改变"哪些格子放什么"。

相关条目