跳到主要内容

4-5 处理按钮事件

判断物品类型

物品的类型信息存储在它的 type 属性中,我们可以通过下面的代码来判断:

if (item.type == Material.BARRIER) {
    // 是屏障(对应重新部署)
}

if (item.type == Material.FIREWORK_ROCKET) {
    // 是烟花火箭(对应快速起飞)
}

// ...

但是像这样写很多 if 会很麻烦,Kotlin 提供了另一种方式来做相同的事情:

when (item.type) {
    Material.BARRIER -> {
        // 是屏障
    }

    Material.FIREWORK_ROCKET -> {
        // 是烟花火箭
    }

    Material.COMPASS -> {
        // 是指南针
    }

    Material.COMMAND_BLOCK -> {
        // 是命令方块
    }

    else -> {
        // 是其它东西
    }
}

when() 内的值与 {} 列出的值从上到下逐个进行匹配,并执行第一个匹配到的值后跟随的 {} 中的代码,这比写一串 if 要方便得多。

接下来只要在各个 {} 内填入相应的代码就可以了!这么一想还是挺振奋人心的,那么我们就来看真正有趣的部分。

实现按钮功能

重新部署

重新部署也就是令玩家死亡并在重生点重生。由于重生是自动的,所以只需要令玩家死亡就可以了。你或许会想去调用 kill 命令,但在插件中,可以直接操作 Player 对象的生命值:

clicker.health = 0.0

系统会判定生命值为 0 的实体为死亡,这么做 OK。

快速起飞

在插件中,要操作玩家的运动,通常都是通过修改玩家的动量(Velocity)

val vec = clicker.velocity                          // 获取动量值
vec.y += config.getDouble("liftoff.velocity", 5.0)  // 在 Y 轴上增加指定的那么多动量
clicker.velocity = vec                              // 重新设置玩家的动量

getDouble 方法从配置文件中获取类型为 Double 的值,即双精度小数。我们首先通过 velocity 取得动量,增加它 Y 轴上的值,再把它重新赋给 velocity,玩家就会受到一个向上的力(实际上是动量),从而向上飞起。+= 代表向指定的值增加那么多东西

5.0 对于动量而言是一个相当大的值,在只需要很少位移的时候,通常会使用 0.5 这样的值。另外即使值是整数,后面的 .0 也不能省略。

信息

为什么不能直接通过 clicker.velocity.y 来操作动量呢?哈,这就是 Getter 转换的问题了。velocity 属性对应 Java 中的 getVelocity 方法,但这个方法获取到的并不是动量对象本身,而是它的一个副本。如果直接通过 clicker.velocity 操作,改变的仅仅是那个复制品,必须在修改后对 velocity 属性重新赋值(实际上是调用 setVelocity 方法)才能保存修改。

不得不说,Kotlin 在这方面确实有一些误导性,因为它并不会真的检查 Getter 和 Setter 的实际功能,仅仅是通过名称来进行猜测。不过相比它带来的便捷性,这一点问题还是可以接受的。

查询延迟

Bukkit 提供了 Player.getPing() 方法来获取延迟,在 Kotlin 中,通过 Player 对象的 ping 属性就可以获得。我们将延迟包装成消息发送给玩家:

clicker.sendMessage(Component.text("Ping: ${clicker.ping}ms"))

执行命令

要以玩家身份执行命令,只需要调用 Player 对象的 performCommand 方法:

clicker.performCommand(config.getString("command.run", "help")!!)

我们从配置文件中提取 command.run 指定的命令执行。这里使用 help 作为默认值,即如果管理员没有配置命令,那么就默认显示帮助信息。

关闭物品栏

一般来说,菜单按钮在点击一次后就会自动关闭。所以,在一切都完成后,我们需要关闭物品栏:

iv.close()

在这些都做完后,事件监听器的完整代码如下:

class EventHandlers(
    private val config: ConfigurationSection
) : Listener {

    @EventHandler
    fun onInventoryClick(ev: InventoryClickEvent) {
        val iv = ev.clickedInventory ?: return                  // 判断所点击物品栏是否为 null
        if (iv.holder is StartMenuInventoryHolder) {            // 判断是否是我们创建的物品栏
            val clicker = ev.whoClicked as? Player ?: return    // 获取点击物品栏的玩家

            val item = ev.currentItem ?: return                 // 获取被点击的物品
            ev.isCancelled = true                               // 防止物品被拿走

            when (item.type) {                                  // 根据物品类型执行操作
                Material.BARRIER -> clicker.health = 0.0

                Material.FIREWORK_ROCKET -> {
                    val vec = clicker.velocity
                    vec.y += config.getDouble("liftoff.velocity", 5.0)
                    clicker.velocity = vec
                }

                Material.COMPASS ->
                    clicker.sendMessage(Component.text("Ping: ${clicker.ping}ms"))

                Material.COMMAND_BLOCK ->
                    clicker.performCommand(config.getString("command.run", "help")!!)

                else -> {}
            }

            iv.close()      // 关闭物品栏
        }
    }
}

我们稍微简化了一下 when 的用法,即如果 -> 后只有一行,那么就可以去掉 {}

和以前的程序相比,代码长了不少,不过在前面的说明下,整体的逻辑应该还算清晰,不会有什么难以理解的地方。

接下来要做的事情就是增添命令处理器,以处理用户输入的 /menu 命令了。不过在此之前,让我们再稍微多了解一点 Kotlin 的另一特性……