任务数据模型
TaskNH 全部任务数据由 6 个 Java 类构成,全部位于 com.eldrinn.tasknh.data。这些类同时是 NBT 存档单元和网络封包单元——toNBT() / fromNBT() 与 writeToBuf() / readFromBuf() 成对存在,字段一一对应,因此下面的 NBT 键名与网络字段是同一套。
Task — 任务主体
data/Task.java。持有 id(final UUID,永不变),其余字段可变。
| 字段 | 类型 | NBT 键 | 写入条件 | 含义 |
|---|---|---|---|---|
id |
UUID |
idMost / idLeast(各 long) |
总是 | 任务唯一标识 |
title |
String |
title |
总是 | 标题,网络侧上限 256 字符 |
description |
String |
description |
总是 | 描述,网络侧上限 4096 字符 |
status |
TaskStatus |
status(存 name() 字符串) |
总是 | 见下方 TaskStatus |
assignees |
List<AssignedPlayer> |
assignees(TAG_List of compound) |
总是 | 负责人,网络侧上限 100 人 |
location |
TaskLocation(可空) |
hasLocation(boolean)+ location(compound) |
hasLocation 总是写;location 仅在非空时写 |
地图标记坐标 |
iconItem |
ItemStack(可空) |
iconStack |
仅在非空时写 | 列表图标,存为完整 ItemStack NBT,因此仅 NBT 不同的两个物品各有各的图标 |
trackItem |
ItemStack(可空) |
trackStack |
仅在非空时写 | 自动完成的目标物品 |
trackItemCount |
int |
trackItemCount |
仅当 > 1 时写 | 需要携带的数量 |
completeOnChecklist |
boolean |
completeOnChecklist |
总是 | 清单全勾后自动完成,默认 false |
showOnMap |
boolean |
showOnMap |
总是 | 是否在 Navigator 地图上显示,默认 false |
parentId |
UUID(可空) |
parentMost / parentLeast |
仅在非空时写 | 父任务;只允许一层嵌套 |
order |
int |
order |
总是 | 同级手动排序位置,小的在前 |
checklist |
List<ChecklistItem> |
checklist |
总是 | 清单项,网络侧上限 200 条 |
comments |
List<Comment> |
comments |
总是 | 评论,网络侧上限 50 条 |
trackItemCount 单独存的原因:ItemStack 的 Count 只有一个字节(上限 64),装不下 1~2304 的范围,因此数量与物品栈分开保存,并由 clampTrackItemCount() 统一夹取。
MAX_TRACK_ITEM_COUNT = 2304(36 * 64)。源码注释说明:一个主物品栏有 36 格、每格最多 64 个,再多的数量永远无法达成;堆叠上限低于 64 的物品自然到不了这个数。
order 的编号域:位置在「同一个分组内」独立编号,分组 = 一个标签页的根任务,或一个父任务的子任务。Task.endOrder() 算出同组内现有最大位置 + 1,服务端与客户端都用它放置新任务,保证客户端显示的位置就是服务端存档的位置。
TaskStatus — 状态枚举
data/TaskStatus.java,三个常量,顺序即网络封包用的 ordinal:
OPEN— lang 键tasknh.status.open→ “Open”(待办标签页)IN_PROGRESS—tasknh.status.in_progress→ “In Progress”(进行中标签页)DONE—tasknh.status.done→ “Done”(完成标签页)
fromNBT(String) 用 valueOf 解析,失败则回退到 OPEN 并打 warn 日志。源码注释:模组更新重命名枚举值后,旧存档里会出现未知状态。
AssignedPlayer — 负责人
data/AssignedPlayer.java,Java record(@Desugar,由 jabel 在 Java 8 目标下编译)。
| 字段 | NBT 键 | 说明 |
|---|---|---|
playerId (UUID) |
most / least |
玩家 UUID(注意是 most/least,不是 Task 的 idMost/idLeast) |
assignedAt (long) |
assignedAt |
分配时间戳(System.currentTimeMillis());hasKey 为假时回退 0L |
assignedAt 与存档里的 playerLastSeen 比较,用来在玩家下次登录时列出「新分配」的任务。
ChecklistItem — 清单项
data/ChecklistItem.java。
| 字段 | NBT 键 | 写入条件 | 含义 |
|---|---|---|---|
id (UUID) |
idMost / idLeast |
总是 | 项唯一标识 |
title |
title |
总是 | 标题,网络侧上限 256 字符 |
checked |
checked |
总是 | 是否已勾选 |
trackItem (ItemStack 可空) |
trackStack |
仅非空时写 | 携带该物品即自动勾选 |
trackItemCount (int) |
trackItemCount |
仅 > 1 时写 | 需携带数量,同样走 clampTrackItemCount() |
trackOre (String) |
trackOre |
仅非空时写 | 矿辞典名;空串表示对 trackItem 精确匹配 |
trackOre 的来源:只有 BetterQuesting 导入会设置它——从 NEI 拖进来的栈只指向一个具体物品而非一个标签。设定后该矿辞典名下的任何物品都算满足,而 trackItem 仍保留作为槽位绘制的显示物品。读取时长度上限 256(与网络侧一致)。
加载兼容:在追踪功能存在之前保存的清单项没有 trackStack / trackItemCount / trackOre 这三个键,加载后即为「不追踪」状态。
Comment — 评论
data/Comment.java,Java record:author(String,网络上限 64)、timestamp(long)、text(String,网络上限 2048)。NBT 键同名 author / timestamp / text。无独立 id 字段。
添加时软上限 50 条(Task.comments 字段注释);网络侧 readFromBuf 对 commentCount > 50 直接抛 IOException。
TaskLocation — 坐标
data/TaskLocation.java:NBT 键 dim(int)、x / y / z(int)、label(String,网络上限 256)。
⚠️ 导出格式与 NBT 格式的字段顺序不一致:TaskLocation.toNBT() 写 dim 在前;/tasknh export 的 JSON 写 x / y / z / dimension / label,而 import 处构造 TaskLocation 的实参顺序是 (x, y, z, dimension, label)——与构造器签名 (dimension, x, y, z, label) 相反。源码中 import 分支调用的是全限定名 new com.eldrinn.tasknh.data.TaskLocation(loc.get("x"), loc.get("y"), loc.get("z"), loc.get("dimension"), ...),因此导入的任务坐标与维度是错位的。详见 导出与导入。
网络侧长度与数量上限
Task.readFromBuf() 中所有上限(超限抛 IOException,而非静默截断):
title256、description4096assigneeCount0–100trackItemCount1–2304checklistCount0–200commentCount0–50status按 ordinal 索引,越界抛IOException
读取物品栈走 Task.readStack():调 readItemStackFromBuffer() 后再检查 stack.getItem() != null,为假则按「无物品」处理。源码注释解释:注册表里没有的 id 会构造出一个背后没有物品的栈,第一次绘制或取名就会抛异常。
向后兼容:两代旧格式
Task 保留了三条旧键兼容路径,全部在 fromNBT() 内实现,读到旧键后下次保存即改写为新格式:
- 物品:旧键
iconItem/trackItem是"modid:item:meta"字符串,由parseLegacyItem()解析;新键iconStack/trackStack存在时优先。parseLegacyItem在分段少于 3 段时返回null。 - 清单改名:旧键
subtasks,新键checklist。String checklistTag = tag.hasKey("checklist") ? "checklist" : "subtasks"; - 缺省回退:
trackItemCount无键时取 1;completeOnChecklist/showOnMap无键时getBoolean返回false;order无键时取 0(全部为 0,因而保持原有顺序)。
trackItemCount 在加载时总是过一遍 clampTrackItemCount()。源码注释:手工编辑过的存档可能存着任意数值,而越界的数量在客户端解码时会失败,所以载入时夹取。
自动完成判定
shouldCompleteOnChecklist() 返回 completeOnChecklist && 清单非空 && 每一项都已勾选。源码注释特别说明空清单永不完成:空清单在平凡意义上「每一项都勾了」,否则一勾上该标志任务就会立刻完成。
completeOnChecklist 的判定放在服务端执行(UpdateTaskPacket.executeServer),而不是客户端。源码注释:这样物品追踪勾上最后一个框时,走的是同一条路径。