配方导出流程
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() 每个栈),但对无序/有序合成是稳定排序,不改变"哪些格子放什么"。
相关条目
- Open Exporter GUI - 打开本流程的唯一热键
- 导出输出位置 -
.json/.zip写到哪里、文件如何命名 - 导出 JSON 格式 -
sources结构与字段缩写约定 - 仅客户端模组 - 导出行为为何只在客户端存在