paper 編 part1 コマンド補完 

基本中の基本でありながら早速引っ掛けがあったのでメモ

🎵 本日の一曲

pv がしぬほどかわいい。

CommandExecutor vs TabExecutor

---
config:
  class:
    hideEmptyMembersBox: true
---
classDiagram
direction TB
    class CommandExecutor {
	    onCommand()
    }
    class TabExecutor {
	    onTabComplete()
    }

	<<Interface>> CommandExecutor
	<<Interface>> TabExecutor

    TabExecutor --|> CommandExecutor

import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor

class ExampleCommand : TabExecutor {
    override fun onTabComplete(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): List<String?>? {
        TODO("Not yet implemented")
    }

    override fun onCommand(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): Boolean {
        TODO("Not yet implemented")
    }
}

TabExecutor を実装すれば、CommandExecutor と TabExecutor 両方を実装することになります
TabExecutor には、onTabComplete というメソッドがあります。 これはコマンド補完機能を提供するものです。

基本的にはTabExecutorを使おう!

Tab 補完あれこれ

onCommand メソッドに関する情報はたくさんあると思うので、ここでは onTabComplete のメモです

基本的な使い方はこんな感じ

override fun onTabComplete(
    sender: CommandSender,
    command: Command,
    label: String,
    args: Array<out String>
): MutableList<String>? {
    return when {
        args[0] == "add" -> Material.entries.filter { it.isBlock }.map { it.name.lowercase() }.toMutableList()
        args.isEmpty() -> "add,del,list".split(",").toMutableList()
        else -> null
    }
}

kotlin の場合は MutableList で返すことになります。 とりあえず java では ArrayList、rust では mut Vec ですね。
whenなのを生かして、when の結果をそのまま return に流してます。

kotlin を使うメリット 1 ですね。

note

rust の使い勝手に近いならそれはメリットですよね!(圧)

onTabComplete が呼ばれるタイミング

実際に調べてみます。

package org.adw39.examplePlugin2

import org.adw39.examplePlugin2.commands.ExampleCommand
import org.bukkit.plugin.java.JavaPlugin

class ExamplePlugin2 : JavaPlugin() {

    override fun onEnable() {
        getCommand("example")?.setExecutor(ExampleCommand())
    }

    override fun onDisable() {
        // Plugin shutdown logic
    }
}
package org.adw39.examplePlugin2.commands

import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor

class ExampleCommand : TabExecutor {
    override fun onTabComplete(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): List<String?>? {
        p3?.forEach {
            println(it)
        }
        return null
    }

    override fun onCommand(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): Boolean {
        return true
    }

}

tip

array は配列なので、forEach で中身を分解して出力します。

tip

内部に Logger オブジェクトが存在しますが、println も使用できます。(多分非推奨)

resources/plugin.yml

name: MyPaperPlugin
version: 0.0.1
main: org.adw39.examplePlugin2.ExamplePlugin2
description: An example plugin
author: nikki
website: https://adw39.org
api-version: "1.21.0"
commands:
  example:
    description: テスト
    usage: "/example <arg>"
    permission: org.adw39.examplePlugin2.example

note

ファイルの位置は、package 文をみていただくのが早いかと思います。
複雑になり始めたら tree を載せます。

warning

plugin.yaml の編集を忘れずに!!

実行結果

/コマンド名 コマンド引数1 コマンド引数2... と入力しますが、
実装した Executor を**getCommandで登録した時に用いた名前がコマンド名となります。**

コマンド名が入力され、引数を入力する際に TabExecutor が呼び出されます。
引数の入力が変更される度にonTabCompleteが呼び出されます。 Tab が入力された時に呼び出されるわけではない模様

ブロック名で補完させる

worldeditのようなプラグインを作りたくなった場合、ブロック名の補完は必須です。
最も単純な方補は、Material列挙型を使うことです。

note

minecraft:oak_logのような表記を名前空間付き idなどと呼ばれるそうです。
今回は名前空間を省略いたします。
また、ハーフブロックなどはブロックステータスを持つのですが、
こちらも省略します。

Material 列挙隊をみてみよう

  • ACACIA_BOAT
  • ACACIA_BUTTON
  • ACACIA_CHEST_BOAT ...
  • LEGACY_RECORD_12 Link icon

と、ブロック id が並んでいます。

また、メソッドにはisAirisBlockなど、便利そうなものが実装されています。

実際に実装してみる

kotlin では、enum につくentriesが活用できそうです。
これは、一応 getter らしく、()は不要とのこと。 中身はイテレーターになってますです!

isBlockで block に限定し、文字列に直して mutableList で返却すれば良さそうです。

package org.adw39.examplePlugin2.commands

import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor

class ExampleCommand : TabExecutor {
    override fun onTabComplete(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): List<String?>? {
        println(p3?.getOrNull(0) ?: "")
        return if (p3?.size == 1) {
            Material.entries.filter { it.isBlock }.map {it.name.lowercase() }.toMutableList()
        } else {
            mutableListOf("")
        }

    }

    override fun onCommand(
        p0: CommandSender,
        p1: Command,
        p2: String,
        p3: Array<out String>?
    ): Boolean {
        val block = p3?.getOrNull(0) ?: return false
        p0.sendMessage(block)
        return true
    }

}

entries はイテレーターですので、filter や map メソッドが活用できます。

it.isBlockはもはや英語として通じてしまいそうで面白いです。
lowercase()にしてるのは、標準実装されているコマンドsetなどが、lowercase で入力するからです。
(あと入力が楽)

実行例

pic1

まとめ

コマンド補完は便利だから実装しよう。

ご意見募集中

当サイトのリポジトリにて、issue 募集中です!