最初に
- この記事は応用で、メインクラスに何でもまとめてしまうのを避けるのがねらいです。
- コマンド処理の前提知識はあると仮定し、記事も長くなるので色々端折ってます。
-
plugin.yml
の記載も省いてます(既に書いてある体)。
この記事を書いた当時の環境は
- Spigot-API
- Java 11(コンパイルは8)
- APIバージョン 1.16(
api-version
では未指定) - Apache Maven
です。当然ながらBukkit、Paperでも応用できますし、固有の機能などは使用しません。
Java 8やAPIバージョンが多少古くても新しくても大半は動くと思いますので、まずは試して下さい。
ソースコードの扱いについて
この記事に書かれたソースコードを、パブリックドメインとするので自由にお使い下さい。
サポートは一切受け付けない。
資料
- Creating a Simple Command | SpigotMC - High Performance Minecraft
- コマンド - Plugin Tutorial - Minecraft Modding Wiki
- [MineCraftプラグイン]コマンドを受け付けてイベントを発生させる - Qiita
肥大化は必ず来る
コマンドの受け取りイベントならメインクラスのonCommand()
で実装しても良いですが、それはケースによるでしょう。
コマンドが複数になるとコードも膨大になります。
if
文の嵐にしたくないなら1コマンド1クラスで分けちゃった方が良いこともあります。
親となる抽象クラスを書く
1コマンド1クラスと言っても似たような処理をクラス毎に書くのは出来るなら避けたいところです。
そこで便利な抽象クラスを先に書いておく。
これをベースにして、子クラス側ではonCommand()
を処理する感じです。
パッケージも***.***.command
みたいに分けておきます。
package com.stsynthe.bukkit.how2.command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import com.stsynthe.bukkit.how2.HowTo;
public abstract class BaseCommand implements CommandExecutor {
private static HowTo PLUGIN = null;// 直接扱わず、メソッド ***Plugin() で介す
public BaseCommand(HowTo plugin) {
if (this.getPlugin() == null)
this.setPlugin(plugin);
if (this.getInstance() == null)
throw new NullPointerException("Instance is null");
if (this.getCommandName() == null)
throw new NullPointerException("CommandName is null");
this.register();
}
/**
* プラグインのインスタンスをゲット
*
*/
final HowTo getPlugin() {
return BaseCommand.PLUGIN;
}
/**
* プラグインのインスタンスをセットする
*
* @param plugin
*/
final void setPlugin(HowTo plugin) {
if (plugin == null)
throw new IllegalArgumentException("Argument \"plugin\" is null");
BaseCommand.PLUGIN = plugin;
}
/**
* CommandExecutor と TabCompleter の登録
*
*/
public void register() {
PluginCommand c = this.getPlugin().getCommand(this.getCommandName());
if (c != null) {
c.setExecutor(this.getInstance());
if (this.getInstance() instanceof TabCompleter)
c.setTabCompleter((TabCompleter) this.getInstance());
}
}
/**
* 自身のインスタンスを返す
*
*/
abstract BaseCommand getInstance();
/**
* コマンド名をゲットする
*
* @return コマンド名を返す
*/
public abstract String getCommandName();
}
とりあえずベースは最低限と言うことでここまで。内訳は割愛します。
試しにコマンド「test」を実装する。
同パッケージ内にクラスTest
を作成。親(スーパー)クラスにBaseCommand
を指定する。
実装内容は
- コマンド「test」を実装
- プレイヤーチャットで
/test
を送信すると「コマンドを実行しました!」というメッセージを送信、表示させる - 結果にかかわらず、コンソールに「コマンドを実行」のメッセージを表示する
- プレイヤーチャット以外(コンソール)からはコマンド実行結果を失敗(偽)で返す
package com.stsynthe.bukkit.how2.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.stsynthe.bukkit.how2.HowTo;
final public class Test extends BaseCommand {
public Test(HowTo plugin) {
super(plugin);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
boolean result = false;
if (sender instanceof Player) {
Player player = (Player) sender;
player.sendMessage("コマンドを実行しました!");
result = true;
}
this.getPlugin().getLogger().info("コマンドを実行");
return result;
}
@Override
BaseCommand getInstance() {
return this;
}
@Override
public String getCommandName() {
return "test";
}
}
親を抽象クラスにしてあるので以上4つのメソッドは必ず実装しておく。
- コンストラクタは親のコンストラクタを先に実行しておきます。
- 受け取った
plugin
は子クラスで保持しない。既にthis.getPlugin()
で使えます。 -
getInstance()
メソッドも自身のインスタンスを返すだけなのでreturn this;
で固定です。 -
getCommandName()
メソッドは今回コマンド「test」を実装するのでtest
を返してやります。
後はonCommand()
の内容をゴリゴリ書いていくだけです。
メインクラスでインスタンスを生成し、登録実装してもらう
public class HowTo extends JavaPlugin {
@Override
public void onEnable() {
new Test(this);
}
}
もう少し色々やった方が良いですが、最低限だとこれだけで実装できます。
BaseCommand
でメインクラス(上記例ではHowTo
)をスコープさせるように成ってるので、わざわざ子クラス毎でインスタンスを保持しなくて良いようになってます。
また、BaseCommand
はコモンにもなれるので共通のコードがあればそこに書いておけば使い回しが出来ます。あとは煮るなり焼くなりどうぞ。plugin.yml
を省いてあるのでそちらの追記を忘れずに。
最後に
この時期のQiita Advent Calendarは「本番環境でやらかしちゃった人」をよく見てたりしますが参加は初めてです。後で見返すとニッチな記事なので少し場違いな気もしたりしなかったり。
以前だとMinecraft Advent Calendar 2017が有ったのですが近年では見かけなくなりました。
Bukkitにかからず、新型ForgeやFabricの一応のドキュメントは有るし、基礎的な記事はいくらでも出てきますが、「はい、基礎はここまで。応用は各自でねー。」なのが多すぎてお腹いっぱいです。自分もそうですけどJavaの事をあまり知らないから応用に踏み込まなかったり、小技的な記事が少ないのかなとも思ったりしてます。
おまけ:タブ補完TabCompleter
の実装
コマンドのタブ補完になるとQiita内で触れている記事は「CraftBukkit・Spigotプラグインを作るときの小技のような何か - Qiita」ぐらいでした。
今までの流れでTabCompleter
も実装してみましょう。
追加するのは子クラスだけ!
お気づきかと思いますが、抽象クラスBaseCommand
の子クラスにTabCompleter
が実装されているとsetTabCompleter()
を実行してタブ補完を登録してくれます。タブ補完はオプショナルなのでBaseCommand
には実装してません。
実装内容は
- コマンド「check」の実装
- タブ補完では1つ目の引数に「one」「two」「three」のリストを返す
- コマンドを発信したのがプレイヤーならチャットに「コマンドは(プレイヤー名)が実行」を送信
- 引数「three」が指定されるとアイテム「石」を3個インベントリに入れる
- 返り値は実行結果に関わらず
true
とする
package com.stsynthe.bukkit.how2.command;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import com.stsynthe.bukkit.how2.HowTo;
final public class Check extends BaseCommand implements TabCompleter {
private String[] completeList = new String[] { "one", "two", "three" };
public Check(HowTo plugin) {
super(plugin);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
if (args.length == 1 && args[0].equals(this.completeList[2]))
player.getInventory().addItem(new ItemStack[] {
new ItemStack(Material.STONE, 3)// 引数が three なら 石3個を得る
});
player.sendMessage("コマンドは" + player.getDisplayName() + "が実行");
}
return true;// 成功で返す
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1)
return Arrays.asList(this.completeList);
return null;// 1つ目以外の引数には null で返す
}
@Override
BaseCommand getInstance() {
return this;
}
@Override
public String getCommandName() {
return "check";
}
}
後はメインクラスに
public class HowTo extends JavaPlugin {
@Override
public void onEnable() {
new Test(this);
new Check(this);// 追加部分
}
}
を、足して動作確認しましょう。