数据文件

本 mod 有 3 类数据文件,全部位于 config/JustEnoughCalculation/ 下, 用 NBTTagCompound 序列化成 JSON(Utilities.Json.read/write)。

基本信息

属性 值
根目录 <config>/JustEnoughCalculation/(JecaConfig.configDir)
访问类 me.towdium.jecalculation.JecaConfig(路径常量)+ Controller(读写逻辑)
序列化格式 NBTTagCompound → JSON(Utilities.Json)
玩家级 无——不写 playerdata/<UUID>.dat,全部落在 config 目录
读写时机 ClientProxy.postInit() → Controller.loadFromLocal();界面改动后 Controller.writeToLocal()

三类文件

文件 常量 内容 谁写
main.cfg JecaConfig.configFile 4 个配置项 JecaConfig.preInit
record.json JecaConfig.recordFile 会话状态(4 段) Controller.writeToLocal()
default.json JecaConfig.defaultFile 默认配方库 只读(Recipes() 无参构造)
data/<group>.json JecaConfig.getDataFile(group) 玩家自建的配方分组 Controller.export(group) / export()

JecaConfig.dataDir = <config>/JustEnoughCalculation/data/, loadFromLocal() 里会 JecaConfig.dataDir.mkdirs() 确保存在。

⚠️ main.cfg 的 4 个配置项全部无效,详见 配置。

record.json 的 4 个顶层键

Controller 定义了 4 个键常量:

键 类型 内容
KEY_GROUPCRAFT = "groupCraft" RecordGroupCraft 配方分组列表(含每组名与索引)
KEY_CRAFT = "craft" RecordCraft 当前合成会话状态
KEY_PLAYER = "player" RecordPlayer 玩家配方库 + 上次编辑的组名
KEY_MATH = "math" RecordMath 数学计算器状态

writeToLocal() 的写入顺序:groupCraft → craft → player → math。

player 段(RecordPlayer)

键 类型 内容
KEY_RECIPES = "recipes" Recipes 玩家全部配方(按组名分组)
KEY_LAST = "last" String 上次编辑的组名;为 null 时不写入
if (last != null) ret.setString(KEY_LAST, last);
ret.setTag(KEY_RECIPES, recipes.serialize());

RecordPlayer 无参构造会 recipes = new Recipes()——这会去读 default.json。 无参构造仅在 nbt 为 null(文件不存在)时被 loadFromLocal() 调用。

craft 段(RecordCraft)

键 类型 内容
KEY_RECENTS = "recents" NBTTagList 最近使用过的标签
KEY_AMOUNT = "amount" String 数量输入框的原始文本
KEY_INVENTORY = "inventory" boolean 背包读取开关(对应界面上的 BTN_INV_E / BTN_INV_D 按钮)
KEY_MODE = "mode" String 显示模式名(Mode 枚举的 name())

反序列化 KEY_MODE 时会做一次名称解析,失败则回落到 Mode.INPUT。 KEY_AMOUNT 存的是字符串而非数字,因为它是输入框的原始文本。

math 段(RecordMath)

键 类型 内容
KEY_CURRENT = "current" String 当前输入缓冲(未解析)
KEY_LAST = "last" — 上一次结果
KEY_OPERATOR = "operator" — 待执行的运算
KEY_STATE = "state" — 显示状态

见 数学显示模式。

配方库格式(Recipes / Recipe)

Recipes 持有一个 HashMap<String, List<Recipe>> records,键按字母序排序后反序列化:

Set<String> keySet = (Set<String>) nbt.func_150296_c();
keySet.stream().sorted().forEach(i -> { ... });

每条 Recipe 有 3 个 NBTTagList(NBT 类型 10 = compound):

键 语义
KEY_INPUT = "input" 原料
KEY_OUTPUT = "output" 产物
KEY_CATALYST = "catalyst" 催化剂

序列化时会裁掉尾部连续的 EMPTY 标签:

for (int i = ls.size() - 1; i >= 0; i--) {
    if (start || ls.get(i) != ILabel.EMPTY) {
        labels.add(ls.get(i));
        start = true;
    }
}

之后用一个 Utilities.ReversedIterator 反向写回——即去掉尾部空槽后再翻转顺序, 使第一个产物成为数组第一个元素(因为 Recipe.getRep() 取 output.get(0) 作主产物)。

每条非法配方会被捕获并只记警告不中断加载:

try { add(i, new Recipe((NBTTagCompound) r)); }
catch (IllegalArgumentException e) {
    JustEnoughCalculation.logger.warn("Invalid recipe record :" + r);
}

default.json 的加载

Recipes 无参构造:

File file = JecaConfig.defaultFile;
NBTTagCompound nbt = Utilities.Json.read(file);
if (nbt == null) JustEnoughCalculation.logger.info("Failed to load default records at " + file + ".");
else { JustEnoughCalculation.logger.info("Loading default records at " + file + "."); deserialize(nbt); }

失败只 info 级别记一条日志,不影响启动。

本仓库的 src/main/resources 下没有 default.json 模板文件, 它需要玩家或整合包自行提供。

导入 / 导出

Controller.discover() 列出 data/ 下所有文件并读成 List<Pair<String, Recipes>>, 读失败的文件会被 filter(i -> i.two != null) 掉并打 warn。

方法 行为
export(group) 导出单组到 data/<group>.json
export() 导出全部到 data/groups.json(组名写死为 "groups")
inport(recipes, group) 导入,注意方法名源码里就是拼错的 inport

导出成功后聊天栏发 jecalculation.chat.export(带文件绝对路径作为 %s 参数)。 两个 export 重载都先取 Utilities.getPlayer(),为 null 直接静默返回。

inport 会先过滤掉重复项再逐条加入:

recipes.getGroup(group).stream().filter(i -> !hasDuplicate(i)).forEach(buffer::add);
for (Recipe r : buffer) addRecipe(group, r);

loadFromLocal 的占位符特殊处理

boolean s = LPlaceholder.state;
LPlaceholder.state = true;
if (nbt != null) { /* 反序列化 4 段 */ } else { /* 全部 new 空记录 */ }
LPlaceholder.state = s;

反序列化前把 LPlaceholder.state 置 true,结束后还原为原值。 这是为了让占位符标签能在读档时正确解析出百分比信息(见 标签类型与合并)。

当 record.json 不存在或解析为 null 时,四段全部用空构造:

记录 兜底
rGroupCraftClient new RecordGroupCraft(new NBTTagCompound())
rCraftClient new RecordCraft(new NBTTagCompound())
rMathClient new RecordMath(new NBTTagCompound())
rPlayerClient new RecordPlayer() ← 无参,会去读 default.json

访问转换器的初始化

ClientProxy.postInit() 的顺序有讲究:

ILabel.initServer();                                  // 1
ClientCommandHandler.instance.registerCommand(new JecaCommand());
ILabel.initClient();                                  // 2 注册 MERGER / CONVERTER / EDITOR
Controller.loadFromLocal();                           // 3 读档(需要 2 已完成)
NEIPlugin.init();                                     // 4

loadFromLocal() 必须排在 ILabel.initClient() 之后, 因为反序列化要靠 ILabel.SERIALIZER 按 "type" 键分派到正确的标签类。

相关条目