【Kotlin+Spigot】2.5 カウントダウンタイマーを作る

## 0. 初めに
`build.gradle` や `plugin.yml` の設定は終わらせた前提で進めます。
[**プロジェクトを作成する**](https://qiita.com/sya-ri/items/1ecccdba38fd3260f5ff) を参考にメインクラスを作るまで終わらせておいてください。

また、コマンドを作成して進めていきます。
詳しい説明は **[2.2 コマンドを新しく作る](https://qiita.com/sya-ri/items/f69f1489b60650a7dd41)** でしていますので省略します。

## 1. plugin.yml にコマンドを登録する

```yml
commands:
  timer:
    usage: "/<command> [Seconds]"
    description: "カウントダウンします"
```
このようなコマンドを登録しておきます。
秒数を引数としています。

## 2. 準備する

メッセージに色をつけたいので、`getColored` を用意しておきましょう。

```Util.kt
object Util {
    fun getColored(text: String): String {
        return ChatColor.translateAlternateColorCodes('&', text)
    }
}
```

## 3. コマンドの処理を書く
`CommandExecutor` を継承した `TimerCommand` というクラスを作成します。


```TimerCommad.kt
class TimerCommand: CommandExecutor {
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        
    }
}
```

引数の処理をしましょう。整数を受け取るので `getOrNull` と `toIntOrNull` を使います。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val seconds = args.getOrNull(0)?.toIntOrNull()
        if(seconds == null){
            sender.sendMessage(getColored("&c秒数を入力してください"))
            return true
        }
        
    }
```

まずはスケジューラーを使えるようになりましょう。

## 4. スケジューラーを使う

**スケジューラー** というものは、**「何秒後に実行」**や**「何秒毎に実行」**といった処理を行う時に使うものです。
この処理はプラグインに紐づけて行われるので、関数の引数として `JavaPlugin` を必要とします。
プラグインのインスタンスを渡してあげるようにしましょう。

```TimerCommad.kt
// private val plugin: JavaPlugin を足した
class TimerCommand(private val plugin: JavaPlugin): CommandExecutor {
```

使い方を紹介します。
**・1秒後にメッセージを出す場合**

```kotlin
Bukkit.getScheduler().runTaskLater(plugin, Runnable { 
    Bukkit.broadcastMessage("1秒経ちました")
}, 20)
```

**`runTaskLater`** は 遅れて処理を行うというものです。
第１引数として `JavaPlugin` を渡します。
第２引数として 行う処理を書きます。 **`Runnable {}`** の中括弧の中に書きます。
第３引数として 遅らせる時間を書きます。 単位は `Tick` になっています。

| Tick | 秒 |
|:-:|:-:|
| 20 | 1 |
| 1 | 0.05 |

**・15秒毎にメッセージを出す場合**

```kotlin
Bukkit.getScheduler().runTaskTimer(plugin, Runnable {
    Bukkit.broadcastMessage("10秒毎に実行しています")
}, 0, 15 * 20)
```

第１,２,３引数は **`runTaskLater`** と同じです。
第４引数として 実行の間隔を書きます。単位は `Tick` です。

何秒というのを書きたいのであれば、`* 20` を書くのがオススメです。
わざわざ電卓を叩いて計算する必要はありません。

**・5秒毎にメッセージを出し、3回で終了する場合**
今回の内容に似ています。しかし、これが一番難しいのです。
何故なら書き方が大きく変わるからです。処理内でキャンセルできる書き方があります。
勿論、これで統一すればどんな内容にも対応できます。

```kotlin
fun repeat3times(){
    var runCount = 0
    object: BukkitRunnable() {
        override fun run() {
            if(runCount < 3){
                Bukkit.broadcastMessage("${runCount + 1} 回目の実行です")
                runCount ++
            } else {
                cancel()
            }
        }
    }.runTaskTimer(plugin, 0, 5 * 20)
}
```

`Runnable` を使っていると、`cancel()` は使えません。
そして、この書き方は無名クラスというものですが、スケジューラーの定型として覚えてしまって大丈夫です。

実は、`runTaskLater` と `Runnable` を使って同じようなことができるんです。
この書き方は直感的ではないと思う人もいるかもしれないので、
説明を聞いて分からなかった人は上の書き方をすることをオススメします。

```kotlin
fun repeat3times(){
    repeat(3)
}

private fun repeat(sumRunCount: Int, remainRunCount: Int = sumRunCount){
    Bukkit.getScheduler().runTaskLater(plugin, Runnable {
        if(0 < remainRunCount){
            val runCount = sumRunCount - remainRunCount
            Bukkit.broadcastMessage("${runCount + 1} 回目の実行です")
            repeat(sumRunCount, remainRunCount - 1)
        }
    }, 5 * 20)
}
```

**`remainRunCount: Int = sumRunCount`** 関数の引数にはデフォルト値を決めておくことができます。
今回であれば、`repeat(3)` と書いたら `repeat(3, 3)` となります。
**`repeat(sumRunCount, remainRunCount - 1)`** で `remainCount` を１減らしています。
結果的に `remainRunCount` は０になるので、**`if(0 < remainRunCount)`** のおかげで `repeat` が実行されなくなるのです。

## 5. タイトルを送る

[![image.png](https://gamepedia.cursecdn.com/minecraft_ja_gamepedia/thumb/8/80/Title_command.png/300px-Title_command.png?version=35d5ec325d2f1f2ea64822974951e156)](https://minecraft-ja.gamepedia.com/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89/title)

こんな感じに表示するためには `player.sendTitle` というものが使えます。
サーバーの全員に送るので、`Bukkit.getOnlinePlayers().forEach { player -> }` を使います。

```kotlin
    private fun sendTitle(seconds: Int){
        Bukkit.getOnlinePlayers().forEach { player ->
            player.sendTitle(getColored("&9&l$seconds"), "", 0, 15, 0)
        }
    }
```

引数として秒数を受け取ります。
では、これらを使って実際にタイマーを作ってみましょう。

## 6. カウントダウンタイマーを作る

私は、`runTaskTimer` を使った書き方でやってみます。

```kotlin
    private fun countDownUsingLoop(seconds: Int){
        var remainSeconds = seconds
        object: BukkitRunnable() {
            override fun run() {
                if(0 < remainSeconds){
                    sendTitle(remainSeconds)
                    remainSeconds --
                } else {
                    cancel()
                }
            }
        }.runTaskTimer(plugin, 0, 20)
    }
```

先ほどのプログラムとほとんど同じなので、説明は省略します。
これを `onCommand` に実装したら終了です。

```TimerCommand.kt
class TimerCommand(private val plugin: JavaPlugin): CommandExecutor {
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val seconds = args.getOrNull(0)?.toIntOrNull()
        if(seconds == null){
            sender.sendMessage(getColored("&c秒数を入力してください"))
            return true
        }
        countDownUsingLoop(seconds)
        return true
    }

    private fun sendTitle(seconds: Int){
        Bukkit.getOnlinePlayers().forEach { player ->
            player.sendTitle(getColored("&9&l$seconds"), "", 0, 15, 0)
        }
    }

    private fun countDownUsingLoop(seconds: Int){
        var remainSeconds = seconds
        object: BukkitRunnable() {
            override fun run() {
                if(0 < remainSeconds){
                    sendTitle(remainSeconds)
                    remainSeconds --
                } else {
                    cancel()
                }
            }
        }.runTaskTimer(plugin, 0, 20)
    }
}
```

`runTaskLater` を使った書き方も出来る人はやってみましょう。
関数名を変えて両方ともテストしてみるといいでしょう。

## 7. コマンドを登録する

```Main.kt
class Main: JavaPlugin() {
    override fun onEnable() {
        registerCommand("timer", TimerCommand(this))
    }

    private fun registerCommand(label: String, executor: CommandExecutor){
        val command = getCommand(label)
        if(command != null){
            command.setExecutor(executor)
            logger.info("/$label を登録しました")
        } else {
            logger.severe("/$label を登録できませんでした")
        }
    }
}
```

では、サーバーでテストしてみましょう。

![count_down.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628547/f900d69c-ae4f-288c-b8de-2fac86691c42.gif)

お疲れ様でした。

## リンク
<!-- ### [次の記事に進む]() -->
### ~~次の記事に進む~~
### [目次に戻る](https://qiita.com/sya-ri/items/bc7eb01ed687b9ff9566#%E7%9B%AE%E6%AC%A1)