Skip to content

通用组件

坏黑 edited this page May 29, 2022 · 2 revisions

通用组件

由 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

注意!命令的逻辑必须在 executeexecuteAsPlayer 代码块中实现。

接下来实现 /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 设置变量 a1

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

分别可以通过 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
    }
}

监听器

通过 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>

目录

开发资源

相关问题

Clone this wiki locally