命令
command/TaskNHCommand.java,继承 CommandBase,在 FMLServerStartingEvent 里用 event.registerServerCommand(new TaskNHCommand()) 注册。客户端不可用(gui 子命令在控制台执行时只回一句提示)。
基本信息
| 项 | 值 |
|---|---|
| 命令名 | tasknh |
getRequiredPermissionLevel() |
0(注释:子命令各自做 OP 限制,所以顶层不设门槛) |
| 用法串 | /tasknh <list|reload|gui|create <title>|assign <id> <player>|unassign <id> <player>|done <id>|export [name]|import <name>|open <taskId>> |
| Tab 补全 | 仅第一层,且只列出当前玩家有权限的子命令 |
权限节点见 权限。所有子命令开头都是 if (!TaskNHPermissions.has(sender, XXX)) → 回 tasknh.cmd.no_permission 并 return。
10 个子命令
| 子命令 | 参数 | 权限节点 | 行为 |
|---|---|---|---|
list |
无 | tasknh.list |
列出本队任务,格式 [UUID 前 8 位] 标题 (状态名);无队则 not_in_team,无任务则 no_tasks |
reload |
无 | tasknh.reload |
给所有在线玩家的队伍重发一次 SyncAllTasksPacket,然后回 tasknh.cmd.synced |
gui |
无 | tasknh.gui |
给执行者单发 OpenGuiPacket();非玩家执行则 tasknh.cmd.gui_client_only |
create |
<title...>(剩余参数用空格拼接) |
tasknh.create |
新建 TaskStatus.OPEN、描述为空串的任务,UUID.randomUUID(),落 addTask(自动排到末尾),推全队 |
assign |
<id> <player> |
tasknh.assign |
加负责人;已分配则回 already_assigned 并直接 break;目标收到一条 tasknh.chat.assigned |
unassign |
<id> <player> |
tasknh.unassign |
移除负责人,回 unassigned_from |
done |
<id> |
tasknh.done |
置 DONE;仅在状态确实变化时 moveToEnd |
export |
[name] |
tasknh.export |
导出 JSON,见 导出与导入 |
import |
<name> |
tasknh.import |
导入 JSON,见 导出与导入 |
open |
<taskId> |
tasknh.open |
给执行者单发 OpenGuiPacket(taskId),客户端打开 GUI 并选中该任务 |
任务 ID 用前缀匹配
findTaskByShortId() 的匹配规则是 t.id.toString().startsWith(shortId)——前缀匹配,不是固定 8 位截断比较。因此 /tasknh done a 这样的单字符前缀会命中本队第一个以 a 开头的任务。输出与错误提示里显示的是 UUID 的前 8 位,但解析接受任意长度的前缀。
open 子命令例外:它用 UUID.fromString(args[1]) 解析,要求完整 UUID,解析失败回 tasknh.cmd.task_not_found。
队伍要求
create / assign / open / export / import 都要求执行者有队伍(TeamManager.getTeamByPlayer),否则 tasknh.cmd.not_in_team。所有任务操作作用域是队伍,不存在跨队伍或管理员全服操作。
create / import 额外要求执行者是 EntityPlayerMP,否则 tasknh.cmd.must_be_player。assign / unassign / done 同样检查玩家身份,但 done 的检查是先查任务再判玩家——findTaskByShortId 内部已经要求玩家身份,所以非玩家在 done 路径上同样会被拦下。
reload 与 list 不要求执行者是玩家:list 走 getSenderTasks,非玩家返回 null → not_in_team;reload 则对所有在线玩家无条件重发。
路径穿越防护
export 与 import 都做规范化路径校验:
if (!out.getCanonicalPath().startsWith(dir.getCanonicalPath() + File.separator)) { ... }
export 失败回 tasknh.cmd.export_failed + “invalid path”;import 路径非法时回 tasknh.cmd.file_not_found(注意这里用的是「文件未找到」的文案,不是 import 专属的失败文案)。目录本身是 <世界存档目录>/tasknh,不存在时 export 会 mkdirs(),失败则回 export_failed + “could not create directory”。
其他实现细节
reload的实现注释说明它是「重发每个在线玩家其队伍的当前任务列表」——不重载任何配置文件。assign里用CommandBase.getPlayer(sender, args[2])解析目标名,目标必须在线(该方法找不到会抛异常/报错)。done对已是DONE的任务重复执行是幂等的(wasDone为真则不moveToEnd),仍会回一条marked_done。create的标题是String.join(" ", Arrays.copyOfRange(args, 1, args.length)),所以多词标题需要自己加引号。