指令树与注册机制
基本信息
| 属性 | 值 |
|---|---|
| 指令总数 | 137(136 个 @Command 方法 + 1 个显式注册的 /set) |
| 顶层根指令 | 6 组 |
| 命令图构建入口 | com.sk89q.worldedit.extension.platform.CommandManager |
| 命令图构建行号 | CommandManager.java:136-210 |
| 分发器获取 | CommandManager.getDispatcher()(CommandManager.java:388) |
| 向 Forge 注册 | ForgePlatform.registerCommands(Dispatcher)(ForgePlatform.java:154-183) |
@Command 注解总数 |
136(全仓 grep -rn '@Command(' src/main/java 计数,两种写法交叉验证一致) |
注册流程
WorldEdit 不使用 @Mod 的 preInit 直接注册指令,而是构建一棵流式命令图(fluent command graph),最后把根节点的子命令逐个包装成 Forge ICommand 注册。
CommandManager构造时调用new CommandGraph().builder(builder),链式调用.commands()得到根DispatcherNode,随后逐个.registerMethods(...)与.group(...)(CommandManager.java:136-210)。.registerMethods(obj)通过ParametricBuilder.registerMethodsAsCommands反射扫描对象上所有带@Command的方法并注册(DispatcherNode.java:82-87)。.group(alias...)创建一个子分发器并注册到当前节点,返回一个 parent 指向当前节点的新DispatcherNode(DispatcherNode.java:102-105)。.parent()返回父节点(DispatcherNode.java:114-120)。ForgePlatform.registerCommands遍历dispatcher.getCommands()—— 该方法只返回直接子节点,不展平嵌套(SimpleDispatcher.java:73-76),因此只有 6 个根组被注册进 Forge。
// 与 / 前缀规则
这是 WorldEdit 1.7.10 指令拼写最容易误解的一点。规则由注解别名本身是否以 / 开头决定:
- 别名以
/开头(如/set)→ 注册名含一个斜杠,玩家在聊天框需再打一个斜杠,即//set - 别名不以
/开头(如version)→ 注册名即别名,玩家输入/version
机制:ForgeWorldEdit.onCommandEvent 与 CommandWrapper.getCommandName() 直接使用别名原文(CommandWrapper.java:43-45 返回 command.getPrimaryAlias()),Forge 端命令名照单全收。同一 registerCommand 调用的多个别名共用一个 SimpleCommandMapping,getCommands() 基于 commands.values()(HashSet)去重,因此一个根组只向 Forge 注册一次,其余作为 getCommandAliases() 返回(SimpleCommandMapping.java:47-49,CommandWrapper.java:47-50)。
6 个顶层根指令
| 根指令 | 别名 | 注册行 | 内容类 | 命令数 |
|---|---|---|---|---|
/worldedit |
we |
CommandManager.java:158-161 |
WorldEditCommands(5) + ForgeRotationCommands(1) |
6 |
/schematic |
schem、//schematic、//schem |
CommandManager.java:163-165 |
SchematicCommands |
5 |
/snapshot |
snap |
CommandManager.java:167-169 |
SnapshotCommands |
5 |
/brush |
br |
CommandManager.java:171-173 + :174-202 |
BrushCommands |
7 |
/superpickaxe |
pickaxe、sp |
CommandManager.java:203-204 |
SuperPickaxeCommands |
3 |
/tool |
(无) | CommandManager.java:206-208 |
ToolCommands |
9 |
ToolCommands被注册两次:一次作为根指令(CommandManager.java:150),一次作为/tool组的子命令(CommandManager.java:208)。因此//tree与/tool tree均可使用。
根节点直接注册(非 // 组内)的类共 15 个(CommandManager.java:138-151),贡献 119 个 @Command 方法;另有 1 个 /set 以 .register(adapt(new SelectionCommand(...)), "/set") 显式注册(CommandManager.java:152-157),它不带 @Command 注解。
权限
每个 @CommandPermissions("...") 值在注册时经 ForgePlatform.registerCommands 逐个登记进 ForgePermissionsProvider(ForgePlatform.java:162-181)。首个别名取 permissions.get(0) 绑定到具体命令,其余别名绑定 null(仅登记权限名)。未标注 @CommandPermissions 的命令不需要权限节点——@Command 的 desc/usage 之外无隐含权限检查。
//we reload 与 //we reloadmappings 共用权限节点 worldedit.reload(WorldEditCommands.java:73-74、ForgeRotationCommands.java:25-26)。
事件挂钩
指令执行前,Forge 的 CommandEvent 被 ForgeWorldEdit.onCommandEvent 拦截,转成 WorldEdit 自己的 CommandEvent 发到事件总线(ForgeWorldEdit.java:201-216);CommandWrapper.addTabCompletionOptions 把补全请求转成 CommandSuggestionEvent(CommandWrapper.java:54-70)。详见 事件总线与事件类。