純規の暇人趣味ブログ

手を突っ込んで足を洗う

[Bukkitプラグイン制作講座-其之三]コマンドに応答する

      2016/12/13    HimaJyun

他のネタが山ほどあるので少し間が空いてしまいましたね、他のネタが沢山あったから間が空いただけで飽きた訳ではありませんよ。

さて、前回はメインクラスの作成とビルドを行いました、どの様な順番で進むのが一番理解しやすいかチュートリアルなどを参考に考えているのですが、とりあえず今回は「動いた」と言う実感を感じてもらうためにコマンドに応答してみようと思います。

前回の記事は「[Bukkitプラグイン制作講座-其之二]メインクラスの作成、ビルド迄」です。

plugin.ymlの編集

自作のプラグインがコマンドに応答する様にするためにはplugin.ymlを編集する必要があります。

僕の手元の(今までの記事通り進んだ)plugin.ymlは以下のようになっています。

main: jp.jyn.sample1.Sample1
name: ${project.artifactId}
version: ${project.version}

検索で訪れた(前回の記事をご覧では無い)方のために軽く説明しておくと、project.artifactIdやproject.versionはMavenのマクロです。

そこに以下の様な物を追記します。

commands:
  <コマンド名>:
    description: <コマンドの説明>

今回は「sample」と言うコマンドを使用出来る様にしてみようと思うので、以下のように設定してみました。

main: jp.jyn.sample1.Sample1
name: ${project.artifactId}
version: ${project.version}

commands:
  sample:
    description: Sample command.

「description」の部分は色々設定出来るのですが、大抵の場合は他の方法でやった方が楽なので今回はdescriptionだけ設定します(場合によってはaliasを設定しておくといいかもしれません)

とりあえずこれで「sample」と言うコマンドにプラグインが応答する準備は整いました、次へ進みましょう。

Tips:複数のコマンド

今回は基本的に「sample」コマンドに応答する事を目的として説明しますが、あなたは「example」コマンドも使用出来る様にしたいと思い立ちました。

その場合は以下の様にすれば良いのです。

commands:
  sample:
    description: Sample command.
  example:
    description: Example command.

注意点として、タブ文字は使用できないと言う事です、ymlファイル内でTabキーは押さない様に意識した方が良いです。(うっかり押してしまうとエディタの自動インデント機能でタブが紛れ込んでしまいます。)

ymlに慣れてくるとその内意味が分かるでしょう、今は深く考えない方が良いかと思います。

コマンドに反応してみる

そこそこ並大抵の規模のプラグインであればコマンドはクラスを分割した方が良いのですが、と、この一言で既にチンプンカンプンでしょうから、今回はクラス分割については説明しません。

まずはプラグインのメインクラス(「extends JavaPlugin」を記載したファイル)を開きましょう、今までの制作講座通りに進んでいると仮定して、以下のようになっているとします。

package jp.jyn.sample1;

import org.bukkit.plugin.java.JavaPlugin;

public class Sample1 extends JavaPlugin {
	@Override
	public void onEnable() {
		getLogger().info("Hello, world!");
	}
}

「getLogger().info("Hello, world!");」はもう必要ないので消してしまっても構わないでしょう。

とりあえずここに以下の様に追記します。

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	return false;
}

すなわち、以下のようになる訳ですね。

package jp.jyn.sample1;

import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;

public class Sample1 extends JavaPlugin {
	@Override
	public void onEnable() {
	}

	@Override
	public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
		return false;
	}
}

これでもうあなたのプラグインはコマンドに応答しているのです、なにもしていないだけで……

何かやってみる

何か……そうですねぇ……???、とりあえず「メッセージを送ってみる」としましょうか。

onCommandメソッドの中(return false;の上)に「sender.sendMessage("Command, world!");」と記載してみて下さい。(Command, world!の部分は何でも良いですが……)

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	sender.sendMessage("Command, world!");
	return false;
}

これをビルドして実行(起動して「sample」コマンドを使用)すると「Command, world!」とメッセージが表示されるかと思います。
bukkit-plugin-development-lecture-3-001

senderはメソッドの引数として渡されたCommandSender型の云々、とにかく「sender.sendMessage("");」はとても良く使うので覚えておきましょう。

Tips:複数のコマンド

さきほど、plugin.ymlで「sample」と「example」コマンドの2つが使える様にする方法をチラッとご紹介しました。

ですが、今のままでは「sample」コマンドを使っても「example」コマンドを使っても「Command, world!」と表示されます。

これでは2つのコマンドが使える様にした意味がないため、処理を分岐する必要がありますね。

分岐は普通にifです(switchでも一応出来ますけど)、問題は条件式です。

ユーザが使用したコマンドは「cmd」変数に送られてきます、「cmd.getName()」とするとユーザが使用したコマンドが文字列として取得出来ます。

文字列の比較は「equals()」ですね、ただしこれは大文字小文字を区別するため「equalsIgnoreCase()」を使いましょう。

要は以下のようにするわけです。

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	if (cmd.getName().equalsIgnoreCase("sample")) {
		// sampleコマンドの時の処理
		sender.sendMessage("Command, sample!");
	} else if (cmd.getName().equalsIgnoreCase("example")) {
		// exampleコマンドの時の処理
		sender.sendMessage("Command, example!");
	}
	return false;
}

Tips:return false?

ところで、先程からさりげなく存在しているおまじない、「return false;」が気になりませんか?

これはコマンドが成功したか失敗したかをBukkitに教えてやるための命令で、trueなら成功、falseなら失敗です。

仮に結果がfalseだった場合はplugin.ymlのコマンド設定に記載出来る(今回はやっていませんが)using:項目のメッセージが表示されます。

チュートリアルにて

onCommand()をコーディングする際は、上記の例のように、メソッドの最終ステップにfalseをreturnする行を記述するのが良い方法です。
falseが返る事で、plugin.yml(下記参照)内に記述されたメッセージが表示され、
onCommand()処理が正しく動作しなかった事をメッセージから検知できるため、動作確認の助けになるからです。
逆に、メソッドの最後でtrueを返す処理構造にしてしまった場合は、onCommand()内の各処理に対して、
処理結果チェック処理と結果が不正である場合にfalseを返すような、同じ処理を何度も記述する必要が出てきますので、大変な無駄となります。

と示されているのでreturn false;にして(おかないと、どこから砲撃が飛んでくるか分からないのでそうして)いますが、僕は常にreturn true;として、正しく動作しなかった場合(引数が足りなかった、など)は設定から読み込んだエラーメッセージを表示する様にしています。(この手法が正しいかどうかは別として)

サブコマンドに対応してみる

せっかくですのでサブコマンドに対応してみましょう。
サブコマンドとは「/sample sub」などの場合の「sub」の部分の事です。

サブコマンド、と当たり前の様に呼んでいますが、これは要は引数を利用した分岐です。

引数とは「/command args a b c」などの「args a b c」の部分です、良くある「java -jre minecraft_server.jar nogui」もjavaコマンドの引数として-jre、minecraft_server.jre、noguiの3つを渡しているのです。

多くのプラグインはこのサブコマンドで様々な機能を切り替えています、そうした方がコマンド1個1個をplugin.ymlに記入する手間も無く、とても楽です。

今回は仮に「a」と「b」をサブコマンドとして、それ以外の時は使い方を表示する様にしてみましょう。

引数の取得

コマンド実行時の引数は「args」と言う変数に詰め込まれて送られてきます、これはString型の配列です。

配列なのでargs[x]などとすれば取得できます、xの部分は0から始まり、1ずつ増えて行きます。(すなわち「/sample a b c」ならaが0、bが1、cが2、と言った具合に)

大事な事なのでもう一度言いますが、xの部分は0から始まります。(本来args[0]であるべき所をargs[1]にしてぬるぽになる人はとてもとてもとてもとても多いです)

ただし、引数が無かった(「/sample」で実行された)時はargs[0]はnullになり、equalsIgnoreCaseをやろうとしてもぬるぽが出るので、まず最初にargsの長さ(配列に入っている要素の数)を確認する必要があります。

早い話、以下のようにすれば良い訳です。

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	if (args.length != 0) {
		// 引数がある
	}
	return false;
}

少々慣れた方であれば少し独特の書き方に見えるかも知れませんが、こうしておけば「引数がない、または未知の引数」の時に使い方を表示するのが楽です。(もちろん引数の数がきちんと判定できるのであればこの形にこだわる必要はありませんが)

(args.length != 0はargs.length > 0でも動作は同じです、ただ、比較演算子は<,<=,>,>=があって個人的に混乱しやすいのであえてこうしています。)

サブコマンドの分岐

さて、先程の手法で「安全に」引数を取得出来る様になりました。

それではさっそくサブコマンドに対応してみましょう。

文字列の比較は「equals()」ですが、コマンドは大文字小文字を区別しないので「equalsIgnoreCase()」を使う、これはもうパターンです。

ifの中はargs配列の長さが非0、すなわち、最低1つは要素がある訳ですので、args[0]がnullになる可能性を考慮する必要性はありません。

今回の場合は「サブコマンドはaとb、それ以外は使い方を表示」ですので以下の様になります。

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	if (args.length != 0) {
		// 引数がある
		if(args[0].equalsIgnoreCase("a")) {
			// aコマンドが呼ばれた
			sender.sendMessage("SubCommand, a!!");
			return true;
		} else if(args[0].equalsIgnoreCase("b")) {
			// bコマンドが呼ばれた
			sender.sendMessage("SubCommand, b!!");
			return true;
		}
	}
	// それ以外(使い方)
	sender.sendMessage("/sample a - aCommand");
	sender.sendMessage("/sample b - bCommand");
	return false;
}

各サブコマンド(args[0].equalsIgnoreCase()の部分)でreturn true;する事でそれ以上先の処理は行われません。(もっと優雅なやり方もあるっちゃありますが……)

ですので、どのサブコマンドとも一致しない、もしくは引数が無い場合は「// それ以外」の所に処理が流れて、使い方が表示される、と言う訳です。

Tips:実はこういうやり方もある

とは言え、サブコマンド毎に「return true;」をするのは決して優雅ではありません。

要はNullPointerExceptionさえ出なければ良いのでもっと楽をしてやりましょう、最小の手数で最大限の効果を発揮する事こそが優雅と言う事です。

少し工夫する事で楽に安全に処理を行う事が出来ます。

@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
	// nullチェック
	String subCmmand = args.length == 0 ? "" : args[0];
	if (subCmmand.equalsIgnoreCase("a")) {
		// aコマンドが呼ばれた
		sender.sendMessage("SubCommand, a!!");
	} else if (subCmmand.equalsIgnoreCase("b")) {
		// bコマンドが呼ばれた
		sender.sendMessage("SubCommand, b!!");
	} else {
		// それ以外(使い方)
		sender.sendMessage("/sample a - aCommand");
		sender.sendMessage("/sample b - bCommand");
	}
	return false;
}

「// nullチェック」の部分が見た事のないカオスな呪文に見えるかも知れませんが、これは三項演算子と言って「条件式 ? trueの時の値 : falseの時の値」で、条件式に応じてどちらかの値を返せます。

要は以下の処理を1行で書いたようなものです。

String subCommand;
if (args.length == 0) {
	subCommand = "";
} else {
	subCommand = args[0];
}

すなわち、argsの長さが0なら空文字を、そうでなければargs[0]の値をsubCommand変数に入れています。

でもって、そのsubCommandをequalsで判断する、と言う訳です、こうすればnullの時は空文字が入っているのでぬるぽを出さずにチェックが出来ます。

でもこの三項演算子、何故か「読みづらい」と言う理由で一定数のアンチ派な人間が存在するのですが、普通に使えば綺麗にまとまって読みやすいと、(個人的には)思うんですけどねぇ……(たかが分岐して変数に値を入れる如きに5行も6行も使ってる方がよっぽど読みづらいですよ)

本当はもっと沢山色々とダラダラ書けるのですが、ソースコード含め7000文字超えちゃっていますし、コマンドの扱い方を1回で説明し切るのは無理があるので今回はこれくらいにしましょう。

其之四は「[Bukkitプラグイン制作講座-其之四]もっとコマンド!!」です。

今回の記事のソースコードはGitHubからご覧いただけます。

 - プログラミング , ,