命令

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)),所以多词标题需要自己加引号。