Minecraft Kotlinプラグイン開発 中級編 Part.3 GUIのクリック検知

## 初めに
今回は前回作成したチェストGUIにクリック検知を追加していきます。

"**クリック検知**" というと複雑に聞こえますが
実際はインベントリ内のアイテムがクリックされた時に、
何らかのアクションを起こさせるというものなので
あまり難しく考えずに作ってみましょう！

**前** → [中級編 Part.2 チェストGUIの作成](https://qiita.com/tooooowa/items/0e03ed15504d9b253898)
**次** → [中級編 Part.4 GUIのクリック検知](https://qiita.com/tooooowa/items/7eeee6c2c1b8f5d29b8c)

**目次** →  [目次と今後の展望](https://qiita.com/tooooowa/items/7809ec65dee757570250)

### 今回作る内容のまとめ
今回作る内容をフローチャートにしてみたのが以下の画像です。
ifを使う場所が2回あり、複雑な処理ですが頑張って作りましょう！

![カスタムGUIフローチャート.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682428/03d08ce6-f5b6-aaa9-c9eb-8a16eeef1669.jpeg)

## プレイヤーのインベントリを保存する変数の作成
<ins>[前回](https://qiita.com/tooooowa/items/0e03ed15504d9b253898)</ins>作成したGUIの中のアイテムが取れないように
アイテムのクリックをキャンセルする処理を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682428/7a015f58-c487-2019-7075-090fb1634c19.png)

インベントリがクリックされた時に
そのインベントリが`/gui`で開かれた物かチェックするために
まずはプレイヤーのインベントリを保存する変数を作成します。

今回は**コマンドのファイルとクリック検知のファイルが異なるため**
全体で使える`companion object`を使用します。
今回使う変数は[中級編Part.1](https://qiita.com/tooooowa/items/e823499575c6500f2ab5)で使った変数とは違います。

:::note info
`companion object`: メインクラスで定義、どのファイルからでも編集可能。
**※プラグインリロードで中身は消えます**
:::

### メインクラスに定義
メインクラスにて変数`guiMap`を定義します。
ここで定義する変数は`Map`という種類で、キーと値の2つセットで保存できます。
`Map`ではキーで値を呼び出す事ができて、他の変数とは少し変わった使い方ができます。

ここではキーでUUID、値でインベントリを保存します。

```TutorialPlugin.kt
package com.github.tooooowa.tutorialplugin

import org.bukkit.inventory.Inventory
import org.bukkit.plugin.java.JavaPlugin
import java.util.*

class TutorialPlugin : JavaPlugin() {
    // ここから下を新たに追加
    companion object{
        val guiMap: MutableMap<UUID, Inventory> = mutableMapOf()
    }
    // ここまで
    override fun onEnable() {
        // Plugin startup logic
        server.pluginManager.registerEvents(EventListener, this)
        getCommand("debug")?.setExecutor(DebugCommand)
        getCommand("sum")?.setExecutor(SumCommand)
        getCommand("gui")?.setExecutor(GuiCommand)
    }
    override fun onDisable() {
    }
}
```
完成形は上の文です。

追加したのは以下の文のみです。
```kotlin
companion object{
    val guiMap: MutableMap<UUID, Inventory> = mutableMapOf()
}
```
普段の`val 変数名`のあとに`: MutableMap<UUID, Inventory>`と書いてあります。
これは変数の型を指定しています。
今プラグインを書いている言語**Kotlin**では基本的には型の指定がいりませんが、
Mapを使う時はキーと値の型に関して記述が必須です。

`= mutableMapOf()`の部分では空っぽのMapを変数に入れています。
Mapの宣言ではよく型の指定、空っぽの代入のセットで使うので覚えておきましょう。

### コマンドの処理に追加
GUIを開いた時に、今作成した変数にGUIを保存します。

```GuiCommand.kt
package com.github.tooooowa.tutorialplugin // この行は環境によって異なります

import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack

object GuiCommand: CommandExecutor {
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        if (sender !is Player){
            return false
        }
        val inventory = Bukkit.createInventory(sender, 9 * 3, "§bメニュー")

        val diamond = ItemStack(Material.DIAMOND, 1)
        val diamondMeta = diamond.itemMeta
        diamondMeta?.setDisplayName("§b青い宝石")
        diamond.itemMeta = diamondMeta
        inventory.setItem(12, diamond)

        val emerald = ItemStack(Material.EMERALD, 1)
        val emeraldMeta = emerald.itemMeta
        emeraldMeta?.setDisplayName("§a緑色の宝石")
        emerald.itemMeta = emeraldMeta
        inventory.setItem(14, emerald)

        TutorialPlugin.guiMap[sender.uniqueId] = inventory // この行を新たに追加
        sender.openInventory(inventory)
        return true
    }
}
```
上の文が完成形です。新しく追加したのは下から5行目の以下の文のみです。
```kotlin
TutorialPlugin.guiMap[sender.uniqueId] = inventory
```
`companion object`の変数を他のファイルから呼び出すには、
`クラスの名前.変数名`で呼び出し可能です。

`Map`に値を代入する時は`変数名[キー] = 値`の形で代入します。
今回はキーとして`sender.uniqueId`(実行者のUUID)を使用しています。
値には上で使用した変数`inventory`を代入しています。

## クリック時のイベントの追加
インベントリをクリックされた時に呼び出される関数を作成します。
[入門編Part.3](https://qiita.com/tooooowa/items/4aed0ea7e2cf2b55f134)で使用した`EventListener.kt`に新しく処理を追加していきます。

```EventListener.kt
package com.github.tooooowa.tutorialplugin

import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.player.PlayerJoinEvent

object EventListener: Listener {
    @EventHandler
    fun onJoin (event: PlayerJoinEvent) {
        event.joinMessage = "§e${event.player.name}がサーバーに参加しました。"
    }
    // ここから下に追加
    @EventHandler
    fun onInventoryClick (event: InventoryClickEvent) {
        if (TutorialPlugin.guiMap[event.whoClicked.uniqueId] == event.inventory) {
            event.isCancelled = true
        }
    }
    // ここまで
}
```
上のではクリックのキャンセルまで新たに実装しました。

前回作った`onJoin`関数の下に`onInventoryClick`関数を追加しています。
また、関数の中にインベントリがguiMapに保存されてるものかチェックする処理と
`event.isCancelled = true`という文を追加しました。

一部のイベントではこの`isCancelled`を`true`に変更することで
イベントのキャンセル(その行動が起きていない状態に戻す)事が出来ます。

例えばブロックが設置された時にキャンセルしてブロックを置けなくしたり、
アイテムの右クリックをキャンセルしてアイテムを右クリックできなくしたり、
色んな使い方が出来るので覚えておきましょう。

:::note warn
`@EventHandler`はイベントの関数ごとに必須なので付け忘れに注意しましょう
:::

この状態で一度ビルドして実行してみます。
`/gui`でアイテムが取れなければ成功です。

出来たらクリックした際に何か起こるようにします。

今回はダイヤをクリックされた時にゲームモードがクリエイティブ、
エメラルドだとサバイバルに変更されるようにします。

```EventListener.kt
package com.github.tooooowa.tutorialplugin

import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.player.PlayerJoinEvent

object EventListener: Listener {
    @EventHandler
    fun onJoin (event: PlayerJoinEvent) {
        event.joinMessage = "§e${event.player.name}がサーバーに参加しました。"
    }
    @EventHandler
    fun onInventoryClick (event: InventoryClickEvent) {
        if (TutorialPlugin.guiMap[event.whoClicked.uniqueId] == event.inventory) {
            event.isCancelled = true

            // ここから下に追加
            if (event.currentItem == null) return

            // ダイヤモンドのときの処理
            if (event.currentItem!!.type == Material.DIAMOND) {
                event.whoClicked.gameMode = GameMode.CREATIVE
            }

            // エメラルドのときの処理
            if (event.currentItem!!.type == Material.EMERALD) {
                event.whoClicked.gameMode = GameMode.SURVIVAL
            }
            // ここまで
        }
    }
}
```
上の文が完成形です。
`event.currentItem`というのがインベントリ内のクリックされたアイテムです。

まずは追加した1行目の`if (event.currentItem == null) return`で
クリックされたアイテムが`null`(空)じゃないかを確かめて
`null`(空)だったら`return`(処理終了)させています。

その後、`event.currentItem.type`(クリックされたアイテムの種類)によって
場合分けして処理を実行しています。

このインベントリクリックのイベントでは
`event.whoClicked`がクリックしたプレイヤーになっています。
ほかとは少し違っていて特殊なので気をつけましょう。

## デバッグ
これをサーバーに入れて、コマンドを実行してみます。
クリックしてゲームモードが変われば成功です。
(音がないので少し分かりにくいかもしれないです)

## 終わりに
今回はチェストを使用したGUIでクリックを検知する方法を学習しました。
また、イベントのキャンセルに関しても導入してみました。
自分でGUIまで作れるようになるとかなり自由度が高いプラグインを作れると思います！

初級編、中級編と少しずつTutorialPluginのコードが増えてきて読みづらくなってきました。
あともう少しで初級編、中級編で使ってきたプラグインは完成させて、
そこから上級編では毎回プラグインを新しく作ろうと思います。

毎週一本ずつ記事を投稿していて少し雑になってしまっていますが
なるべく時間がある時に一度投稿した記事を編集したりしたいと思っていますので
これからもよろしくお願いします！

とりあえず今回は終了です。お疲れ様でした！

**前** → [中級編 Part.2 チェストGUIの作成](https://qiita.com/tooooowa/items/0e03ed15504d9b253898)
**次** → [中級編 Part.4 GUIのクリック検知](https://qiita.com/tooooowa/items/7eeee6c2c1b8f5d29b8c)

**目次** →  [目次と今後の展望](https://qiita.com/tooooowa/items/7809ec65dee757570250)

## 参考
[https://www.youtube.com/watch?v=hPzg4tXcu5I](https://www.youtube.com/watch?v=hPzg4tXcu5I)
[https://www.youtube.com/watch?v=MenxQ76Weho](https://www.youtube.com/watch?v=MenxQ76Weho)