客户端图钉配置

config/PinnedTasksConfig.java,@SideOnly(Side.CLIENT)。这是一份手写 JSON 而非 Forge .cfg——它存的是纯客户端界面状态(图钉、折叠、HUD 位置),不需要服务端同步。与 服务端配置 是两个互不相干的文件。

文件位置

项 值
当前文件 <mcDataDir>/config/tasknh_pins.json
旧文件(一次性迁移源) <mcDataDir>/config/foreman_pins.json(模组原名 “Foreman”)

Gson 用 new GsonBuilder().setPrettyPrinting().create()。load() 读文件,save() 写文件;每次增删图钉/折叠/改 HUD 都会立即 save(),只有拖动 HUD 的偏移用 setOffsetXRaw / setOffsetYRaw 只写内存,注释要求调用方在拖动结束时 save()。

JSON 结构

顶层 4 个键(Data 内部类):

键 类型 默认 说明
worlds Map<String, WorldEntry> 空 map key 是服务端下发的世界 UUID 字符串(WorldIdPacket),一个存档一项
hud HudPosition 见下 HUD 位置与显示选项,全局一份,不按世界分
pinnedTasks List<String> null 旧格式残留,迁移后置 null,Gson 写盘时跳过
foldedTasks Map<String, String> null 同上

WorldEntry 每项两个键:pinnedTasks(List<String>,任务 UUID 字符串,有顺序)与 foldedTasks(Map<String, String>,父任务 UUID → Fold 名称,只存非默认值 SHOW_ALL 的项)。

HudPosition 的 8 个键与默认值:

键 类型 默认 范围 / 说明
anchor String(Anchor 名) "TOP_RIGHT" 九宫格锚点,非法值回退 TOP_RIGHT
offsetX int 0 相对锚点的 X 偏移
offsetY int 0 相对锚点的 Y 偏移
scale double 1.0 setScale 夹取到 0.5–2.0
showBackground boolean true
hudVisible boolean true
maxChecklistShown int 3 setMaxChecklistShown 夹取到 1–10
maxSubtaskRowsShown int 3 setMaxSubtasksShown 夹取到 1–10
maxPinnedTasks int 5 setMaxPinnedTasks 夹取到 1–10

Anchor 枚举九值:TOP_LEFT / TOP_CENTER / TOP_RIGHT / MIDDLE_LEFT / MIDDLE_CENTER / MIDDLE_RIGHT / BOTTOM_LEFT / BOTTOM_CENTER / BOTTOM_RIGHT。

Fold 枚举三值,即折叠按钮的循环顺序:SHOW_ALL → HIDE_DONE → HIDE_ALL。setFold 在值为 SHOW_ALL 时直接从 map 删除该键,所以默认状态不占空间。

旧键迁移

load() 做两件事:

  1. 换文件名——tasknh_pins.json 不存在而 foreman_pins.json 存在时,直接读旧文件,置 migrating = true,最后 save() 写到新文件名。
  2. 搬平铺的图钉/折叠——setWorld(worldId) 时把 data.legacyPinnedTasks 追加进该世界项、把 data.legacyFoldedTasks 合并进去,然后置 null。源码注释:搬进「第一个加入的世界」,Gson 跳过 null 字段所以旧键自然消失。

maxSubtasksShown → maxChecklistShown 改名:旧键是一个 Integer 类型字段(legacyMaxSubtasksShown),load() 中检测到非空就赋给 maxChecklistShown 并置空。源码注释特别澄清命名:新键叫 maxChecklistShown,而 maxSubtaskRowsShown 是另一个东西——清单(checklist)与子任务(subtask)在 TaskNH 里是两个独立概念,各有各的显示上限。

resetToDefaults() 把 9 个 HUD 字段全部写回上表默认值并 save()。

防御性处理

这份配置是可以手改的,代码因此处处设防:

  • getAnchor() 与 getFold() 用 valueOf 包 try/catch,非法枚举名回退默认值
  • setWorld 里对 entry.pinnedTasks / entry.foldedTasks 做 null 检查——源码注释:手改过的文件可能带显式 null,而 Gson 会保留它们
  • removeStale(Set<UUID> existing) 用 UUID.fromString 解析并把解析失败的条目也算作 stale 一并删掉(源码注释 remove malformed entries too)。每次同步后调用,只清理当前世界,其他世界的条目不动

图钉上限的判定

pin() 自身不检查上限,源码注释:「上限由调用方检查,因为调用方知道状态——已 DONE 的任务不占名额。」实际检查在 TaskNHClientCache.canPin()。lang 里 tasknh.gui.pin.max=Max 5 tasks pinned 是硬编码的 5,而配置默认 maxPinnedTasks 也是 5,但该文案不随配置变化。

图钉列表是有序的(List 而非 Set),HUD 按此顺序绘制。