-
Notifications
You must be signed in to change notification settings - Fork 5
通用组件
由 Artifex 内置的几种组件,独立脚本或工程脚本均可以使用。
通过 TabooLib 提供的函数注册命令,具体表现为:
command("hello") {
execute { sender, _ ->
sender.sendMessage("Hello World!")
}
}
标准命令注册
通过顶级函数 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 { sender, _ ->
sender.sendMessage("HelloWorld")
}
}
可以看到这个命令只有一层,用户在输入 /command
时得到提示 HelloWorld
。
注意!命令的逻辑必须在
execute
或executeAsPlayer
代码块中实现。
接下来实现 /command [玩家]
命令向该玩家发送 HelloWorld
信息。
command("command") {
// 二级参数入口。玩家名称是不固定的,所以使用 dynamic 代码块。
// 通过添加 optional 选项,标记该参数为可选。
// 否则第二个 execute 部分将会作废
dynamic(optional = true) {
// 玩家名称补全
suggest { onlinePlayers().map { it.name } }
// 命令逻辑
execute { sender, args ->
getProxyPlayer(args.argument())!!.sendMessage("HelloWorld")
}
}
execute { sender, _ ->
sender.sendMessage("HelloWorld")
}
}
这样以来,我们便完成了对该命令的升级。输入 /command [玩家]
执行第一个 execute
部分,发送信息给该玩家,不使用参数直接输入 /command
则执行第二个 execute
部分发送信息给自己。相信你可以理解这样的结构。
现在,我需要实现 /command all
命令向所有玩家发送 HelloWorld
信息。
command("command") {
literal("all", optional = true) {
// 不使用的参数省略为一个下划线
execute { _, _ ->
onlinePlayers().forEach { it.sendMessage("HelloWorld") }
}
}
dynamic(optional = true) {
suggest { onlinePlayers().map { it.name } }
execute { sender, args ->
getProxyPlayer(args.argument())!!.sendMessage("HelloWorld")
}
}
execute { sender, _ ->
sender.sendMessage("HelloWorld")
}
}
添加 literal
结构明文规定 all
参数,使用 /command all
将会执行这部分的逻辑。
限制执行人
execute { sender, args ->
// 任何单位可执行
// sender 类型为 ProxyCommandSender
// 可使用 sender.bukkitSender() 转换为 Bukkit 的 CommandSender 类型
}
executeAsPlayer { player, args ->
// 只能被玩家执行
// player 类型为 ProxyPlayer,并非 Bukkit 类型
// 可使用 sender.bukkitPlayer() 转换为 Bukkit 的 Player 类型
}
这完全避免了我们在命令开发过程中的类型判断与转换过程。
限制参数
第一个 execute
中,我们获取玩家时直接使用了 非空断言,而没有进行空指针判断。
getProxyPlayer(argument)!!.sendMessage("HelloWorld")
因为 suggest
代替我们进行了参数判断,在输入补全结果之外的内容将不会执行 execute
部分。若要关闭这个限制,则需要在 suggest 中启用
uncheck` 选项,不进行参数检查。
suggestion(uncheck = true) { onlinePlayers().map { it.name } }
若 dynamic
参数没有提供补全,我们可以使用 restrict
结构来约束输入参数。
dynamic {
restrict { value ->
// 只允许使用数字类型
Coerce.asInteger(value).isPresent
}
}
嵌套结构
无论是 literal
还是 dynamic
都属于命令结构语句,都允许嵌套使用来解释更复杂的命令。
command("command") {
literal("arg1") {
literal("arg2") {
// ...
}
dynamic {
// ...
}
}
}
参数获取
我们以 /var [key] [value]
命令为例,使用 /var a 1
设置变量 a
为 1
。
command("var") {
// 第一个参数 key
dynamic {
// 第二个参数 value
dynamic {
execute { sender, args ->
// 我们仅通过 args 获取命令的上下文
// 使用偏移方式获取参数:
// args.argument(0) 得到当前参数 value
// args.argument(-1) 得到左边第一个参数 key
// 使用序号方式获取参数:
// args.get(0) 得到第一个参数 key
// args.get(1) 得到第二个参数 value
// 当前参数若没有下文,则自动拼接后续内容
// 即用户输入:/var a 11 22 33 则此处 value 参数为 "11 22 33"
// 通过偏移方式获取 key 参数
val key = args.argument(-1)
// 通过偏移方式获取 value 参数
val value = args.argument()
}
}
}
}
重写错误信息
默认情况下,错误信息由 TabooLib 代理发送,具体表现为:
- Incorrect sender for command
- Unknown or incomplete command, see below for error
- Incorrect argument for command
分别可以通过 incorrectSender
与 incorrectCommand
方法重写。
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
}
}
通过 Artifex 提供的函数注册监听器,具体表现为:
// 注册监听器
on<PlayerJoinEvent> { e ->
e.player.sendMessage("HelloWorld")
}
// 注册 HIGH 优先级的监听器
on<PlayerQuitEvent>(priority = EventPriority.HIGH) { e ->
e.player.sendMessage("HelloWorld")
}
可用参数列表
名称 | 作用 | 可用 |
---|---|---|
priority | 优先级 | LOWEST, LOW, NORMAL, HIGH, HIGHEST, MONITOR |
ignoreCancelled | 忽略已被取消的事件 |
通过 TabooLib 提供的函数注册命令,具体表现为:
// 创建一个只运行 1 次的调度器,在服务器完全启动后向控制台打印 HelloWorld 信息
submit {
info("HelloWorld")
}
// 创建一个只运行 1 次的调度器,在 5 游戏刻后启动
submit(delay = 5) {
// ...
}
// 创建一个循环调度器,每 20 游戏刻运行一次
submit(period = 20) {
// ...
}
// 创建一个循环调度器,每 20 游戏刻运行一次,在 5 游戏刻后启动
submit(period = 20, delay = 5) {
// ...
}
// 创建一个只运行 1 次的调度器,在非主线程中运行
submit(async = true)) {
// ...
}
注销调度器
无论是在调度器的内部还是外部,都可以通过 cancel
方法来注销。
submit {
// 注销方法并不会影响本次运行,而是在本次运行结束后停止调度器
cancel()
}
val task = submit {
// ...
}
task.cancel()
同步执行器
在多线程开发工作中,常常需要返回主线程获取数据,如一些地图数据必须在主线程中获取。在 Bukkit 平台中可以使用 callSyncMethod
这类方法,但是并不理想。
// 非主线程
submit(async = true) {
...
val data = sync { world.anySyncMethod() } // 在主线程中执行逻辑
...
}
// 在主线程中运行并等待返回结果
// 在主线程中运行该方法将产生 IllegalStateException 异常
fun <T> syncBlocking(func: () -> T): T
// 在主线程中运行
// 在主线程中运行该方法将产生 IllegalStateException 异常
fun <T> sync(func: () -> T): CompletableFuture<T>
// 在非主线程中运行(由 Bukkit & BungeeCord 提供线程池)
fun <T> async(func: () -> T): CompletableFuture<T>
// 在给定 ExecutorService 中运行
fun <T> async(executorService: ExecutorService, func: () -> T): CompletableFuture<T>