命令

每个平台都有命令(Command)部分,通过 TabooLib 提供的接口,一次编写的命令可以在各个平台上使用,具体表现为:

command("tpuuid") {
    literal("random") {
        execute<ProxyPlayer> { player, _, _ ->
            player.teleport(player.entities().randomOrNull() ?: return@execute)
        }
    }
    dynamic(optional = true) {
        suggestion<ProxyPlayer> { player, _ ->
            player.entities().map { it.toString() }
        }
        execute<ProxyPlayer> { player, _, argument ->
            player.teleport(UUID.fromString(argument))
        }
    }
    execute<ProxyPlayer> { player, _, _ ->
        player.teleport(player.entityNearly() ?: return@execute)
    }
}

或是:

@CommandHeader("tpuuid")
object TpUUID {

    @CommandBody
    val main = mainCommand {
        dynamic(optional = true) {
            suggestion<ProxyPlayer> { player, _ ->
                player.entities().map { it.toString() }
            }
            execute<ProxyPlayer> { player, _, argument ->
                player.teleport(UUID.fromString(argument))
            }
        }
        execute<ProxyPlayer> { player, _, _ ->
            player.teleport(player.entityNearly() ?: return@execute)
        }
    }

    @CommandBody
    val random = subCommand {
        execute<ProxyPlayer> { player, _, _ ->
            player.teleport(player.entities().randomOrNull() ?: return@execute)
        }
    }
}

标准命令注册

通过顶级函数 command 进行命令注册,该函数中除命令名称外的其他参数均为可选。

// 注册 command 命令
command("command") {

}
// 注册 command 命令并添加别名与描述
command("command", aliases = listOf("cmd"), description = "A command") {

}

可用参数列表

名称

作用

aliases

别名

description

描述

usage

使用方式

permission

使用权限(默认为:插件名称.指令.use)

permissionMessage

没有权限的提示信息

permissionDefault

默认拥有权限(该功能目前仅限 Bukkit 平台)

TabooLib 的命令注册与 Bukkit 不同,没有 args 的概念,而是通过逐层的嵌套来完成对命令的解释。

command("command") {
    execute<ProxyCommandSender> { sender, context, argument ->
        sender.sendMessage("HelloWorld")
    }
}

可以看到这个命令只有一层,用户在输入 /command 时得到提示 HelloWorld

Warning

命令的逻辑必须在 execute 代码块中实现。

接下来实现 /command [玩家] 命令向该玩家发送 HelloWorld 信息。

command("command") {
    // 二级参数入口。玩家名称是不固定的,所以使用 dynamic 代码块。
    // 通过添加 optional 选项,标记该参数为可选。
    // 否则第二个 execute 部分将会作废
    dynamic(optional = true) {
        // 玩家名称补全
        suggestion<ProxyCommandSender> { sender, context ->
            onlinePlayers().map { it.name }
        }
        execute<ProxyCommandSender> { sender, context, argument ->
            getProxyPlayer(argument)!!.sendMessage("HelloWorld")
        }
    }
    execute<ProxyCommandSender> { sender, context, argument ->
        sender.sendMessage("HelloWorld")
    }
}

这样以来,我们便完成了对该命令的升级。输入 /command [玩家] 执行第一个 execute 部分,发送信息给该玩家,不使用参数直接输入 /command 则执行第二个 execute 部分发送信息给自己。相信你可以理解这样的结构。

现在,我需要实现 /command all 命令向所有玩家发送 HelloWorld 信息。

command("command") {
    literal("all", optional = true) {
        execute<ProxyCommandSender> { sender, context, argument ->
            onlinePlayers().forEach { it.sendMessage("HelloWorld") }
        }
    }
    dynamic(optional = true) {
        suggestion<ProxyCommandSender> { sender, context ->
            onlinePlayers().map { it.name }
        }
        execute<ProxyCommandSender> { sender, context, argument ->
            getProxyPlayer(argument)!!.sendMessage("HelloWorld")
        }
    }
    execute<ProxyCommandSender> { sender, context, argument ->
        sender.sendMessage("HelloWorld")
    }
}

添加 literal 结构明文规定 all 参数,使用 /command all 将会执行这部分的逻辑。

限制执行人

不论是 execute 还是 suggestion 部分都需要我们定义一个泛型。它作用不仅仅是提供后续方法的补全,还有限制功能。

execute<ProxyCommandSender> { sender, context, argument ->
    // 任何单位可执行
}
execute<ProxyPlayer> { sender, context, argument ->
    // 只能被玩家执行
}
// org.bukkit.entity.Player
execute<Player> { sender, context, argument ->
    // 只能被玩家执行,且自动转换为 Bukkit 类型
}

这完全避免了我们在命令开发过程中的类型判断与转换过程。

限制参数

第一个 execute 中,我们获取玩家时直接使用了 非空断言,而没有进行空指针判断。

getProxyPlayer(argument)!!.sendMessage("HelloWorld")

因为 suggestion 代替我们进行了参数判断,在输入补全结果之外的内容将不会执行 execute 部分。若要关闭这个限制,则需要在 suggestion 中启用 uncheck 选项,不进行参数检查。

suggestion<ProxyCommandSender>(uncheck = true) { sender, context ->
    onlinePlayers().map { it.name }
}

dynamic 参数没有提供补全,我们可以使用 restrict 结构来约束输入参数。

dynamic {
    restrict<ProxyCommandSender> { sender, context, argument ->
        // 只允许使用数字类型
        Coerce.asInteger(argument).isPresent
    }
}

嵌套结构

无论是 literal 还是 dynamic 都属于命令结构语句,都允许嵌套使用来解释更复杂的命令。

command("command") {
    literal("arg1") {
        literal("arg2") {
            // ...
        }
        dynamic {
            // ...
        }
    }
}

参数获取

我们以 /var [key] [value] 命令为例,使用 /var a 1 设置变量 a1

command("var") {
    // 第一个参数 key
    dynamic {
        // 第二个参数 value
        dynamic {
            execute<ProxyCommandSender> { sender, context, argument ->
                // 这里 argument 为字符串类型,仅代表 value 参数的内容
                // 使用 /var a 1 则此处 argument 为 "1"
                // 因此则需要通过 context 来获取命令的上下文
                // 通过 argument 方法取左边一个参数,这里 -1 代表 offset(偏移)而不是 index(序号)
                val key = context.argument(-1)

                // 若使用 /var a 11 22 33 则此处 arguemnt 为 "11 22 33"
                // 若没有子结构,argument 会拼接后续传入的所有参数
                val value = argument
            }
        }
    }
}

重写错误信息

默认情况下,错误信息由 TabooLib 代理发送,具体表现为:

  • Incorrect sender for command

  • Unknown or incomplete command, see below for error

  • Incorrect argument for command

分别可以通过 incorrectSenderincorrectCommand 方法重写。

command("command") {
    // 错误的执行者
    incorrectSender { sender, context ->

    }
    // 错误的命令
    incorrectCommand { sender, context, index, state ->
        // index 为错误参数的位置
        // state 为错误的类型
        // 1 -> Unknown or incomplete command, see below for error
        // 2 -> Incorrect argument for command
    }
}

简化命令注册

除使用 command 顶级函数外,还可以通过注解进行命令注册。基于 依赖注入,命令需要在单例或伴生类中定义。

@CommandHeader("command")
object Command {

    /**
     * 同等于:
     * command("command") {
     *     dynamic {
     *         ...
     *     }
     *     execute<ProxyCommandSender> { sender, context, argument ->
     *         ...
     *     }
     * }
     */
    @CommandBody
    val main = mainCommand {
        dynamic {
            suggestion<ProxyCommandSender> { sender, context ->
                onlinePlayers().map { it.name }
            }
            execute<ProxyCommandSender> { sender, context, argument ->
                getProxyPlayer(argument)!!.sendMessage("HelloWorld")
            }
        }
        execute<ProxyCommandSender> { sender, context, argument ->
            sender.sendMessage("HelloWorld")
        }
    }

    /**
     * 同等于:
     * command("command") {
     *     literal("all", optional = true) {
     *         ...
     *     }
     * }
     */
    @CommandBody(optional = true)
    val all = subCommand {
        execute<ProxyCommandSender> { sender, context, argument ->
            onlinePlayers().forEach { it.sendMessage("HelloWorld") }
        }
    }
}

相比标准命令注册方式,这样省去了定义 @Awake 的过程,使项目更加有序且规范。