Eclipse+Forgeでマイクラのmod開発～　自作コマンドを実装してみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/55cbf795-870b-7c11-c84d-11b9e6c4786f.png)
今回はmodでコマンドを作成してみます。前回の記事からだいぶ間が空いてしまったのは、[今更ながら（Yet Another）JavaでVoIPするためのライブラリ](https://qiita.com/kawamune/items/514dfe92eb3dfa0b5906)の記事に書いたVoIPで遊んでいたから。。またmod開発に戻ってきました。
ちなみに今回は書くことが多かったり、いっぱいサンプルコードが必要だったので長編です。


## 環境
* OS: Windows10
* Jdk: openjdk version "15.0.2" 2021-01-19
※16.xでもいいかもだけどgradleやらなにやら色々変わってしまって心配だったので今回は15で。
* IDE: Eclipse IDE for Java Developers (Version 4.19.0)
* Buildship(Eclipseのgradleプラグイン): 3.0
* Minecraft: Java版 1.16.5
* Forge: 1.16.5

## まずは動かしてみる

やはりまずは簡単なコードを動かしてみるのがわかりやすいでしょう、ということで動かしてみます。

```java:CommandTest.java
package jp.munecraft.mod.commandtestmod;

// importは省略

@Mod("commandtestmod")
public class CommandTest {
	
	private static final Logger LOGGER = LogManager.getLogger();
	
	public CommandTest() {
		LOGGER.info("Hello command test mod");
        MinecraftForge.EVENT_BUS.register(this);
	}

	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.executes(context -> {
				LOGGER.info("hoge command executed");
				return Command.SINGLE_SUCCESS;
			});
		
		event.getDispatcher().register(builder);
	}
}
```

では、さっそく動かしてみる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/5c139c21-9f06-3c2a-7679-3caf69a90563.png" width="400">

お、ちゃんと`h`を打った時点で`hoge`コマンドが候補に出てきて認識しているようですね。`hoge`まで打ってエンターをするとログに`hoge command executed`が出てきたので動いている模様。簡単ですね！

とはいえ一つずつ解説。。

まずはコマンド登録。登録はForgeのイベントバスで`RegisterCommandEvent`を拾って行います。上のコードの中では`onRegisterCommand`メソッドがイベントハンドラですね（イベントハンドリングについて、詳しくは[こちらの過去記事参照](https://qiita.com/kawamune/items/b6aac165885f3ee70291)）。この中でコマンドを組み立てます。この、コマンドの組み立て方が（私には。。）かなりトリッキーなのですが、とりあえず最初の`Command.literal("hoge")`がコマンド名だと思ってくださいませ。

続いての`executes`がコマンド実行時の処理本体です。ここには`CommandContent<CommandSource>`を引数に取るラムダを渡します。今回は`hoge command executed`がログ出力されるだけですが、ここに好きな処理を書いてください。

最後に組み立てたコマンドの実態である`LiteralArgumentBuilder<CommandSource> builder`を`event.getDispatcher().register(builder);`で登録してあげればOKです。

## コマンドの分岐

組込みのコマンド`time`などを使うとき、`time`の後に時間の指定方法がいくつか選択肢として出てくるのを見たことがあると思います。こんな感じ。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/1673e103-1113-286b-6b12-b35d3a42d5b8.png" width="400">

`time`コマンドは時間の指定方法として朝昼晩を指定したり時刻を指定したり時間を進める、といったことができますが、それらを第二引数以降で指定させることができます。このようなコマンドの分岐や引数の指定は前述のコマンドの組み立ての際にあれやこれやすることでできます。

さっそくサンプルコードを見てみましょう。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("fuga")
				.executes(context -> {
					LOGGER.info("hoge fuga command executed");
					return Command.SINGLE_SUCCESS;
				}))
			.then(Commands.literal("piyo")
				.executes(contect -> {
					LOGGER.info("hoge piyo command executed");
					return Command.SINGLE_SUCCESS;
				}));
		
		event.getDispatcher().register(builder);
	}
```

`onRegisterCommand`メソッドだけ書き換えてみました。ちょっと複雑になってきました。正直わかりにくいです。多分何とかパターンとかいうデザインパターンで作られてるんでしょうが普通に`if-else`とか`switch`で書かせてくれた方が可読性も高いしいいと思うのだけど。。。と愚痴ってもしょうがないので解説。（最後にちょっとまとめて愚痴りますｗ）

`then()`メソッドや`execute()`メソッドは`LiteralArgumentBuilder<CommandSource>`のインスタンスを返してくれます。これに対して`then()`やら`executes()`を数珠つなぎにしてコマンドの分岐の構造を定義していきます。上の例ではインデントを気を付けているのでまだ理解しやすいと思いますが、同列に並んでる`then`がコマンドの分岐になります。なので、`hoge`コマンドの後の分岐として`fuga`と`piyo`が出てくると。試しに動かしてみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/1da3ce3b-c362-94de-36d9-e8959462a84a.png" width="400">

うまく動きました。`hoge`を打った後スペースを一つ入れると分岐の`fuga`と`piyo`が出てきますね。このどちらかを入力して実行するとサンプルコードにある通りのログが出力されます。`fuga`や`piyo`の内側、一段ネストされた中に`then()`を並べればさらに複雑な分岐条件も作ることができます。ただ、ネストしまくるとどんどん分岐の構造がわかりにくく。。パーレンの対応関係とインデントに気を付けて設計してくださいませ。。一応一つだけ例を載せておきますね。`fuga`の後を分岐させてみました。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("fuga")
				.then(Commands.literal("hogehoge")
					.executes(context -> {
						LOGGER.info("hoge fuga hogehoge executed");
						return Command.SINGLE_SUCCESS;
					}))
				.then(Commands.literal("piyopiyo")
					.executes(context -> {
						LOGGER.info("hoge fuga piyopiyo executed");
						return Command.SINGLE_SUCCESS;
					})))
			.then(Commands.literal("piyo")
				.executes(contect -> {
					LOGGER.info("hoge piyo executed");
					return Command.SINGLE_SUCCESS;
				}));		
		event.getDispatcher().register(builder);
	}
```

こんな感じです。実行してみると。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/7749af9c-d0e4-5af2-39f5-b7b09341602e.png" width="400">

うまく行ったようですね。ちなみにこの程度のコードを書くだけでもパーレンの対応を何度か確認しながら書きました。。（おっと、また愚痴りそうに）

ちなみにこの`fuga`と`piyo`、コマンドの引数やん！って思われると思います。いや、そうなんですけどね。ただ、Forge（マイクラ自体？）のコマンドではこの`then()`から`Commands.literal()`で指定されたものと、この後で開設する引数（Argument）は明確に扱いが違うのであえて「分岐」と書いています。

## コマンドの引数

続いてコマンドの引数です。コマンドに対して整数値や浮動小数点、文字列、カスタム型の引数を渡すことができます。まずはサンプルコード。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.argument("argumentName", IntegerArgumentType.integer())
				.executes(context -> {
					return executeHoge(context);
				}));
		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		int argumentInteger = IntegerArgumentType.getInteger(context, "argumentName");
		LOGGER.info("hoge command executed: " + argumentInteger);
		return Command.SINGLE_SUCCESS;
	}
```

コードをだいぶすっきりさせました。まず分岐の説明のために複雑に構造化されていた部分はバッサリ消して、`hoge`の後に整数の引数を一つとるようなコードに変えています。また、ラムダの中にごちゃごちゃ処理を書くとわけがわからなくなるので`executeHoge()`メソッドに切り出しました。実行してみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/cab8a93f-f717-9be0-2614-8a7c76984439.png" width="400">

`hoge`の後に数字を入力したところ何やら`<argumentName>`なる表示が上に出るようになりました。そのままコマンドを実行してみます。

```
[01:38:13] [Server thread/INFO] [minecraft/MinecraftServer]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld
[01:38:14] [Render thread/INFO] [minecraft/AdvancementList]: Loaded 0 advancements
[01:38:15] [Server thread/INFO] [minecraft/MinecraftServer]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether
[01:38:15] [Server thread/INFO] [minecraft/MinecraftServer]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end
[01:38:15] [Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: Gathering id map for writing to world save New World
[01:38:15] [Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: ID Map collection complete New World
[01:38:22] [Server thread/INFO] [jp.mu.mo.co.CommandTest/]: hoge command executed: 1234
```

最後の行にログが出ていて、引数の整数がちゃんと受け取れているのがわかりますね。パチパチパチ。では解説。

引数を受け取る場合、`then()`の中で`Commands.argument()`メソッドを呼びます。この`Commands.argument()`メソッドの第一引数（サンプルコードでは`argumentName`）は引数の名前を指定します。あとで引数の値を取り出すときのキーになります。
第二引数には`ArgumentType`型のインスタンスを受け取りますが、このサンプルでは`IntegerArgumentType`を渡しています。`IntegerArgumentType.integer()`はオーバーロードされていて、最小値や範囲を指定した`IntegerArgumentType`を作って渡すことができます。

続いて引数の受け取り側、`executeHoge()`の中を見てみます。ポイントは`int argumentInteger = IntegerArgumentType.getInteger(context, "argumentName");`ですね。みりゃわかるけど。第一引数は`CommandContext`、第二引数は先ほど指定した引数の名前です。これでコマンド実行時に指定した引数を受け取れる、というわけですな。

引数の後に前述の分岐を置くこともできます。分岐にはなってませんが、整数の引数の後に`"fuga"`のliteralが来るようなコードを書いてみます。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.argument("integerArgument", IntegerArgumentType.integer())
				.then(Commands.literal("fuga")
					.then(Commands.argument("stringArgument", StringArgumentType.string())
						.executes(context -> {
							return executeHoge(context);
				}))));
		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		int integerArgument = IntegerArgumentType.getInteger(context, "integerArgument");
		String stringArgument = StringArgumentType.getString(context, "stringArgument");

		LOGGER.info("hoge command executed");
		LOGGER.info("integerArgument : "+ integerArgument);
		LOGGER.info("stringArgument : "+ stringArgument);

		return Command.SINGLE_SUCCESS;
	}
```
実行してみると。。。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/231ece2b-5efa-76b9-fead-6d2ca831acad.png" width="400">

途中の分岐である`"fuga`"が表示されてますね。このまま入力を続けて最後の文字列引数に`"abcd"`を入れてコマンドを実行してみると。。。

```
[13:48:32] [Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: Gathering id map for writing to world save New World
[13:48:32] [Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: ID Map collection complete New World
[13:51:10] [Server thread/INFO] [jp.mu.mo.co.CommandTest/]: hoge command executed
[13:51:10] [Server thread/INFO] [jp.mu.mo.co.CommandTest/]: integerArgument : 4321
[13:51:10] [Server thread/INFO] [jp.mu.mo.co.CommandTest/]: stringArgument : abcd
```

ちゃんと引数も受け取れてますね。

## 引数の型

Forge（マイクラ）ではいくつかの組み込みの型が用意されていて、大体のことはそれらを使えば事足りると思います。筆者も全部は全然使いこなせていないのですが、どこにそれらのクラスがあるのかだけご紹介

### minecraftの組み込み引数

`net.minecraft.command.arguments`パッケージ内に存在しています。ちょっと中身を眺めてみると。。。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/b79e2911-d810-69b4-9982-34042775d44b.png" width="400">

`XxxxxxArgument.class`と命名されているクラスが引数クラスのようですね。名前だけではぱっと見何に使うのか想像がつかないものもたくさんあります。。まぁ、いずれ。。


### brigadierライブラリ

本家のMojangがMITライセンスで公開しているコマンドライブラリです。[gitリポジトリはこちら](https://github.com/Mojang/brigadier)。ただ、Mdkを使っていれば同梱されているので個別に落としてくる必要はないです。中身を少し眺めてみると。。。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/b8bbe003-e690-21e2-31aa-44408260364a.png" width="400">

上の方のサンプルでも使っていた`StringArgumentType`や`IntegerArgumentType`があることが確認できます。他にもBoolとかFloatとか。まだ使ったことないものもありますが、まぁ直感的に使えそうな感じですね。


## 権限

マイクラではコマンドを実行する権限を制御することができます。といっても、現時点で分かっているのはチートOn/Offで使える/使えないを切り替える、という使い方。どうやらもうちょっと色々できそうな感じですが、より詳細な使い方はわかってきたら加筆する、ということで。。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.requires(context -> {
				return true;
			})
			.executes(context -> {
				return executeHoge(context);
			});

		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		LOGGER.info("hoge command executed");
		return Command.SINGLE_SUCCESS;
	}
```

またコードをシンプルに戻しました。ポイントは`Command.literal("hoge")`の後の`requires()`ですね。この`requires()`は`Predicate`を引数に取ります。つまり、ラムダで`bool`を返せばよいと。で、このサンプルコードのように`false`を返すとそのコマンドは使えなくなる（マスクされる）というわけです。動かしてみましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/bce5dd42-0dd0-8633-3b73-f01b7d79a22e.png" width="400">

ほら、`hoge`コマンドが出てこなくなった。もちろん、サンプルコードみたいに常に`false`を返すような実装をすると一生使えないコマンドになるので、使わせたくないときだけ`false`を返せばいいわけです。

ちなみにこのコマンドのマスク、コマンドの分岐でも使えます。例えばこんなコード。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("fuga")
				.requires(context -> {
					return false;
				})
				.executes(context -> {
					return executeHoge(context);
				}))
			.then(Commands.literal("piyo")
				.executes(context -> {
					return executeHoge(context);
				}));

		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		LOGGER.info("hoge command executed");
		return Command.SINGLE_SUCCESS;
	}
```

`"hoge"`の後の`"fuga"`だけマスクしてみました。すると、

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/ffb0614e-c6f7-c3d0-5b24-3ade78602211.png" width="400">

`"fuga"`が選択肢から消えましたね。こんな使い方したいケースはあるかわからないですが、特定の条件下だけで分岐を許す、みたいなこともできそうですね。

でででで、お次は実用的（？）な使い方。権限によるマスクをやってみます。早速コード。。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.requires(context -> {
				return context.hasPermission(4);
			})
			.executes(context -> {
				return executeHoge(context);
			});

		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		LOGGER.info("hoge command executed");
		return Command.SINGLE_SUCCESS;
	}
```

今回のポイントは`requires()`の中の`return context.hasPermission(4);`ですね。あとで解説しますが、これはチートか許可されているかどうかを判定するコードになっています。チートOn/Offで試してみると。。。

### チートOff
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/fe1393ab-6379-8825-6dea-71effc3575ef.png" width="400">
### チートOn
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/3b2f87d7-1038-61ea-f55e-801ea5487978.png" width="400">

チートがOnの時だけしか使えなくなってますね。で、解説。`context.hasPermission(4);`は現在の権限（Permission）が引数（ここでは4）以上の場合だけ`true`を返します。つまり、チートがOnの時は権限が4以上になっているということですね。

「4以上」ということは権限が3や2や1になることもあるのかな。。と思ったんですが、試せた範囲だと以下のようになることがわかりました。

* チートOff : 0
* チートOn : 4

つまり0か4の2択。。。でも、CommandSourceクラスのソースを見ると

```java:CommandSource.java
   public boolean hasPermission(int p_197034_1_) {
      return this.permissionLevel >= p_197034_1_;
   }
```
となっているので、数字が大きくなるほど権限が多い、ということを示唆してますよね。。。現時点では0か4以外の権限になるケースがわかっていないので、この謎はまたわかった時に追記します。。

## おしまい

コマンド編は一応こんなところです。完璧に使いこなそうと思うとMdkやbrigadierのソースを追わないとダメなケースも出てくると思いますが、この記事の基本が理解できていればきっとサクサクと作れるようになるでしょう！（ほんとかな）
最後に参考情報です。自作コマンドを作るうえでは`net.minecraft.command.impl`の中に組み込みのコマンドのコードがいっぱい入っているのでこれらを参考にするとよいでしょう。後述の愚痴に書くように、なかなか読むのもおっくうになるような実装もありますが、まぁ頑張って読み解けば参考になる情報はいっぱい埋まってると思います。

## 愚痴

こっからはmodの開発に直接関係ないです。。が、今回調べててこの作りはひどくね？と思ったところがいくつかあったので愚痴っていこうと思います。。。Mdkもminecraftも私より優秀な人が書いているので的外れな指摘かもですが。。。


コマンドの引数、分岐を`then()`、`requires()`でつないでいきますよね。これが一直線に並ぶならいいと思うんですが、分岐のための木構造をとるのでわけわからなくなるんですよね。例えばこのコード。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("piyo"))
				.executes(context -> {
					LOGGER.info("hoge piyo");
					return Command.SINGLE_SUCCESS;
				})
			.then((Commands.literal("fuga"))
				.executes(context -> {
					LOGGER.info("hoge fuga");
					return Command.SINGLE_SUCCESS;
				}));
		event.getDispatcher().register(builder);
	}
```

正しそうに見えません？実は正しくないっす。実行すると

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/bd39c16d-e100-0464-b94e-9a5e4d01e003.png" width="600">

ありゃりゃ。ランタイムエラーになったっちゃったよ。。。ﾄﾎﾎ

まず正解から。これが正解。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("piyo")
				.executes(context -> {
					LOGGER.info("hoge piyo");
					return Command.SINGLE_SUCCESS;
				}))
			.then((Commands.literal("fuga"))
				.executes(context -> {
					LOGGER.info("hoge fuga");
					return Command.SINGLE_SUCCESS;
				}));
		event.getDispatcher().register(builder);
	}
```

どこが間違ってるかわかります？ここを修正したんです。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.literal("piyo")                 // <- ここ！
				.executes(context -> {
					LOGGER.info("hoge piyo");
					return Command.SINGLE_SUCCESS;
				}))                                        // <- とここ！
			.then((Commands.literal("fuga"))
				.executes(context -> {
					LOGGER.info("hoge fuga");
					return Command.SINGLE_SUCCESS;
				}));
		event.getDispatcher().register(builder);
	}
```

最初の誤ってるコードと比較してみてください。パーレンの対応が間違ってたんですね。つまり、`then()`でコマンドを分岐させる場合、分岐させるもう一つの`then()`の戻り値に対して`then()`してやる必要があるんですが、誤っていたコードでは最初の`executes()`の戻り値に対して`then()`しちゃってるんですよね。

パーレンの対応一個間違っただけで動かないなんて。。。といっても「プログラムなんてそんなもんだろ！」という声も聞こえてきそうですが、この実装がイケてない理由は

### コマンドを実行するまで分岐構造のミスに気付かない

先ほどの誤ったコード、コンパイルはおろかコマンドの登録までできちゃいます。で、誤りに気付くのはコマンド実行時。。しかも出力されるエラー、ログは~~クソ~~非常にわかりづらく、何が間違ってるのかわからんという。。

### 可読性低杉

ここに載せたサンプルコードはかなり意識してインデントしたのでまだ構造が読めると思います。ただ、IDEのインデントに任せてももっとごちゃごちゃしちゃって読めまへん。IntlliJだともっときれいにインデントしてくれんのかいな。。ちなみにさらに絶望したのは組み込みコマンド`time`の実装。まぁ見てよ。

```java:TimeCommand.class
   public static void register(CommandDispatcher<CommandSource> p_198823_0_) {
      p_198823_0_.register(Commands.literal("time").requires((p_198828_0_) -> {
         return p_198828_0_.hasPermission(2);
      }).then(Commands.literal("set").then(Commands.literal("day").executes((p_198832_0_) -> {
         return setTime(p_198832_0_.getSource(), 1000);
      })).then(Commands.literal("noon").executes((p_198825_0_) -> {
         return setTime(p_198825_0_.getSource(), 6000);
      })).then(Commands.literal("night").executes((p_198822_0_) -> {
         return setTime(p_198822_0_.getSource(), 13000);
      })).then(Commands.literal("midnight").executes((p_200563_0_) -> {
         return setTime(p_200563_0_.getSource(), 18000);
      })).then(Commands.argument("time", TimeArgument.time()).executes((p_200564_0_) -> {
         return setTime(p_200564_0_.getSource(), IntegerArgumentType.getInteger(p_200564_0_, "time"));
      }))).then(Commands.literal("add").then(Commands.argument("time", TimeArgument.time()).executes((p_198830_0_) -> {
         return addTime(p_198830_0_.getSource(), IntegerArgumentType.getInteger(p_198830_0_, "time"));
      }))).then(Commands.literal("query").then(Commands.literal("daytime").executes((p_198827_0_) -> {
         return queryTime(p_198827_0_.getSource(), getDayTime(p_198827_0_.getSource().getLevel()));
      })).then(Commands.literal("gametime").executes((p_198821_0_) -> {
         return queryTime(p_198821_0_.getSource(), (int)(p_198821_0_.getSource().getLevel().getGameTime() % 2147483647L));
      })).then(Commands.literal("day").executes((p_198831_0_) -> {
         return queryTime(p_198831_0_.getSource(), (int)(p_198831_0_.getSource().getLevel().getDayTime() / 24000L % 2147483647L));
      }))));
   }
```

これで分岐の木構造が頭の中にコンパイルされた人は~~病気です~~すごいです。なんかのデザインパターンを使ってるのかもしれないですが、ここまでくると乱用じゃね？可読性やらデバッグの容易性を考えたら愚直に`if/else`を並べて構造がそのままわかるような実装方法にすりゃいいと思うんですがね。

### 引数指定誤りのミスに気づけない

次はこんなコード。文字列の引数をとってそれをログに出力するという本編にも書いたようなコードです。

```java:CommandTest.java
	@SubscribeEvent
	public void onRegisterCommand(RegisterCommandsEvent event) {

		LiteralArgumentBuilder<CommandSource> builder = Commands.literal("hoge")
			.then(Commands.argument("stringArgument", StringArgumentType.string())
				.executes(context -> {
					return executeHoge(context);
				}));
		event.getDispatcher().register(builder);
	}

	public int executeHoge(CommandContext<CommandSource> context) {

		String stringArgument = StringArgumentType.getString(context, "stringArgment");
		LOGGER.info("hoge command executed: " + stringArgument);
		return Command.SINGLE_SUCCESS;
	}
```

コマンドを実行してみると。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191672/9744ea36-fc3b-70d0-be28-0305ba48388b.png" width="600">

でました、ランタイムエラー。例によって出力されるエラー、ログは~~クソ~~非常にわかりづらくって何がお起こってるのかわからねぇ。このコード、何が間違っていたかというと、`executeHoge()`内で引数を取得する時の引数のキー`stringArgument`の綴りが誤ってるところ。

キーを`String STRING_ARGUMENT = "stringArgument"`こんな感じで定数にして、引数定義と参照のところで使えばいいじゃん、という声が聞こえてきそうですが。。。これの何がイケてないかというと

### そもそも2か所で同じキーを使わせるところ

上に書いた定数を使う方法は、誤りを回避する方法であって、そもそも2か所で同じキーを使わせないような作りにしてくれればいいのに、と思うわけですよ。例えば、`String stringArgument = context.requireArgument("stringArgument")`みたいに引数定義と同時にその値がとれてしまえばいいわけじゃないかなぁと思うわけですわ。

### 原因が分からなさ杉

今回の誤りの裏で何が起こっていたのか。デバッガを使って調べたところ実は`IllegalArgumentException`がthrowされてます。で、minecraftの実装が見事にその例外をもみ消して~~クソみたいな~~分かりにくいエラーだけ出してくれてます。余計なことをせずにログにStackTraceを出してくれりゃいいのに。。もしくは、`getString()`が存在しないキーを指定された場合はnullを返すような作りになってた方が未だなんぼかわかりやすいと思うんだけどねぇ。。

### で、最後に思ったこと

全ての原因は、マイクラが起動時のイベントでコマンドの構造をすべて組み立てて事前登録させる、という設計になっている点なのかなぁと思いました。なので`then()`の数珠つなぎで木構造を作らせたり、引数のキーを引数定義のところと実行時のラムダの2か所で参照させざるを得なかったとか。。
コマンドの構造を事前登録させたかった理由まではわかりませんが、だったとしてももうちょっとマシにできたんじゃないのかなぁとか思ったり。