客户端图钉配置
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() 做两件事:
- 换文件名——
tasknh_pins.json不存在而foreman_pins.json存在时,直接读旧文件,置migrating = true,最后save()写到新文件名。 - 搬平铺的图钉/折叠——
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 按此顺序绘制。