/help 命令修复

HelpFixer 的核心机制:替换并清洗 Minecraft 的 CommandHelp,同时扫描所有已注册命令的 compareTo 实现。

基本信息

属性 值
触发阶段 服务端启动 (FMLServerStartingEvent) 与启动完成 (FMLServerStartedEvent)
钩入对象 net.minecraft.command.CommandHelp
行为 替换默认实现 + 扫描并警告 compareTo 错误的命令
来源 com.matthewprenger.helpfixer.HelpFixer(modid = HelpFixer,MC 1.7.10)

行为

HelpFixer 在 FMLServerStartingEvent 期间调用 event.registerServerCommand(new CommandHelp() { ... }),用一个匿名子类覆盖原版的 CommandHelp。该子类重写的方法是 getSortedPossibleCommands(ICommandSender),返回执行 /help 时实际列出的命令列表。

随后在 FMLServerStartedEvent 期间,遍历 MinecraftServer.getCommandManager().getCommands().values(),对每个命令调用 validCompareTo(command),把任何 compareTo 实现存在缺陷的命令以 [HelpFixer] 前缀写入 FML 日志告警(不会真正修复或替换它们)。

替换的 CommandHelp.getSortedPossibleCommands 行为

该方法对 MinecraftServer.getServer().getCommandManager().getPossibleCommands(sender) 的返回列表依次做以下处理:

  1. 剔除 getCommandName() == null 的命令(防止 NullPointerException 出现在后面的排序/格式化中)—— 日志 [HelpFixer] Identified command with null name, Ignoring: <类名>。
  2. 剔除 getCommandUsage(sender) == null 的命令 —— 日志 [HelpFixer] Identified command with null usage, Ignoring: <类名>。
  3. 剔除任何 getCommandName() 或 getCommandUsage() 抛 CommandException 的命令 —— 日志 [HelpFixer] Command to throw exception <异常类>, Ignoring: <类名>。
  4. 按命令名字符串对剩余命令执行 Collections.sort(Comparator 直接调用 o1.getCommandName().compareTo(o2.getCommandName()))。

最后返回排序后的列表。这是实际 /help 在屏幕/聊天框中显示的依据。

compareTo 缺陷扫描

检测原理

validCompareTo(ICommand command) 用两个内部测试命令 testCmd1(getCommandName() = "a")与 testCmd2(getCommandName() = "z")做差值对比:

public static boolean validCompareTo(@Nonnull ICommand command) {
    return command.compareTo(testCmd1) != command.compareTo(testCmd2);
}

判定规则

command.compareTo(testCmd1) command.compareTo(testCmd2) validCompareTo 结果
不等于 不等于 true(实现正确)
相等(不论值) 相等(不论值) false(实现错误)

按 ICommand 排序契约,compareTo 应满足 反对称性:当 a < b(按 getCommandName 比较)时,c.compareTo(a) 与 c.compareTo(b) 至少方向相反或绝对值不同。如果命令的 compareTo 重写把这两个比较都返回常量(例如 return 0),就会被检测出来。

触发的日志

[HelpFixer] Command <命令名> incorrectly overrides compareTo: <命令类全名>

仅记录日志,不抛异常、不阻止服务器启动、也不替换实现。被检测的命令如果参与 /help 之外的命令管理器其它排序路径,仍会按错误的 compareTo 排序。

数值

数值 默认值 来源
testCmd1.getCommandName() "a" HelpFixer.java:115
testCmd2.getCommandName() "z" HelpFixer.java:131
MOD_ID "HelpFixer" HelpFixer.java:31
acceptedMinecraftVersions [1.7.10] HelpFixer.java:27
acceptableRemoteVersions * HelpFixer.java:26

限制

  • 未修改被检测命令的 compareTo —— 只在日志里告警,不重写实现。被错写 compareTo 的命令如果进入其他需要排序的代码路径仍然会出错。
  • 不修复命令本身的 getCommandName()/getCommandUsage() 抛 CommandException 的根本原因 —— 只是把该命令从 /help 输出中剔除。
  • 仅 MC 1.7.10 + Forge(acceptedMinecraftVersions = "[1.7.10]")。acceptableRemoteVersions = "*" 允许任意客户端版本连接到服务器。

相关条目