はじめに
マインクラフトのコマンドシステムは、非常に便利かつ複雑な知識は不要で簡単にゲームシステムに干渉できるという点で、老若男女を問わず多くのプレイヤーに愛され、使われています。この記事を読んでいるあなたも、一度は触ったことがあるのではないでしょうか?

さて、そんなコマンドですが、実は自作できるのはご存じでしょうか?
以前、このような記事を投稿しています:
この記事で示している通り、「scriptevent」という少々マイナーなコマンドを介せば、自分でオリジナルのコマンドシステムを構築できることは(ScriptAPI界隈の中では)割と有名だと思います。
しかし、今回紹介する方法はそれとは少し違います。なんと、「コマンドそのもの」を作ってしまうことができるのです!そう、コマンドの候補欄に出てくるやつですよ。

どういうことか
先日、1.21.80へのアップデートが行われました。ポイントはここにあります。
なんと、同アップデートによって、CustomCommand(カスタムコマンド)がサポートされ、ユーザーが自由にコマンドを作るシステムが構築されたのです(以前はできなかった)。これは非常に意義深いことだと思うのですが、巷のYouTuberやSNS民がそれほど騒がないのが不思議です。細かいことは、公式がアナウンスしています:

もちろん現在はベータ版でのみの実装となりますが、非常に夢が広がるシステムですよね?「これは試してみるしかないっ!」ということで、実際に使ってみましょう。
情報がないっ!
解説を始める前に一つ。
実はこのCustomCommandに関する記事、私が探した限りでは巷に日本語・英語を含め情報は全くありません。
ということで、この記事は私の悪戦苦闘の塊です。実機検証は済んでいるのでご安心ください。なんで情報がないんでしょうね?
Youtube動画
本記事の内容について解説したYouTube動画を作成しました。こちらより視聴できます:
前提環境
そもそも、CustomCommand(カスタムコマンド)の機能を使うにはScriptAPIが必要です。ScriptAPIの一関数として実装されているので当然ですね。従って、予めScriptAPIの環境構築を済ませておく必要があります。
「ScriptAPIって何ぞや」という方はまずはこちらをご覧ください。環境のテンプレートを配布しています:
また、現在は実行環境としてバージョン1.21.80以上のマインクラフトが必要です。メニュー画面右下のバージョンを確認してみてください。バージョンがそれを下回っていた場合、アップデートが必要です。

Micosoft Storeから更新できます。

最後に、現在はベータ版でのみの実装ですので、アドオンのmanifest.jsonのminecraft/serverのバージョンがベータ版(執筆時点では2.0.0-beta)である必要があります。指定するバージョンはその都度変わりますので、各自公式のリファレンスで確認すると良いかと思います。

これに伴い、アドオンのフォルダ内で次のコマンドを実行しておくとテキストエディタ上のコード補完機能がついて便利です(実行にはnode.js環境が必要です)。
npm install @minecraft/server@2.0.0-beta.1.21.80-stable

スクリプト
scriptsフォルダ内のmain.jsを開き、まずは以下のimport文によって変数serverを宣言します。既にこれがある場合は不要です。
import * as server from "@minecraft/server";
続いて、カスタムコマンドを定義するスクリプトを記述していきます。以下は、「lq:test」という名前のコマンドを定義する例です:
server.system.beforeEvents.startup.subscribe(ev => {
ev.customCommandRegistry.registerCommand({
name:"lq:test",
description:"これはテスト用のコマンドです",
permissionLevel : server.CommandPermissionLevel.Any,
mandatoryParameters:[
],
optionalParameters:[
]
},(origin, arg) => {
origin.sourceEntity.sendMessage("テスト用文章")
})
})
systemクラスのstartup Classを使うのが肝ですね。情報が全くなくて苦労しました。
上記のnameでコマンド名を定義しています。ここをアレンジすることで、オリジナルのコマンド名をつけることができます。ただし、コマンド名は「(名前空間):(コマンド名)」である必要があるので、注意してください。今回でいえば、名前空間はlq、コマンド名はtestですね。
続いて、description部分でコマンドの説明文を付与できます。これがそのまま、ゲーム内でコマンドを入力する際の候補部分に表示される文章になります。
permissionLevelはこのコマンドの権限レベルです。これが今回のようにAnyの場合、全てのプレイヤーが使うことができますが、HostやAdmin、Ownerなどに設定して制限をかけることもできます。
mandatoryParametersとoptionalParametersは後述します。
最後に引数originとargを受けてsendMessage関数でメッセージの表示を行っています。この部分が、「実際にコマンドを実行したときに発火する関数」を定義する部分となります。なお、beforeEventsプロパティを使用している都合上、runCommand関数は使えないので注意してください(使うとエラーを吐きます)。なお、runCommandAsyncは同アップデートで削除されています。
実際に確かめる
さて、上記のスクリプトを書いて保存し、実際にゲーム内での挙動を確認してみましょう。このアドオンの有効化は当然として、今回ベータ版を使用している都合上、実験的な機能の「ベータAPI」と「今後のクリエイター機能」をオンにする必要があります。

その後、ワールドを起動してチャット欄に先ほど定義したコマンドを入力してみましょう。おそらく、候補欄にカスタムコマンドが表示されているはずです:

実際にこれを実行すると・・・

このように、「テスト用文章」という表示が出ることが確認できると思います。
どうしてもrunCommandが使いたいっ!
はい、お気持ちわかります。runCommand、使いたいですよね?
実はsystemクラスのrunTimeout関数を用いて意図的に関数の実行を遅延させることで、無理やりrunCommandを使うことができるんです!
ということでrunCommand関数をあえて使ったスクリプトは以下:
server.system.beforeEvents.startup.subscribe(ev => {
ev.customCommandRegistry.registerCommand({
name:"lq:test",
description:"これはテスト用のコマンドです",
permissionLevel : server.CommandPermissionLevel.Any,
mandatoryParameters:[
],
optionalParameters:[
]
},(origin, arg) => {
server.system.runTimeout(() => {
origin.sourceEntity.runCommand("say test")
},1)
})
})
functionとの違い
さて、マインクラフトにはfunctionと呼ばれる、予め定義しておいた一連のコマンド群を一斉に処理できるシステムがあります。一見するとこのfunctionとCustomCommandは定義していますが、実は決定的な違いがあるのです:

上で示した通り、functionは所詮コマンドの集まりですので、if文やfor文といったプログラミング的制御構造は有していません。それに対して、CustomCommandはScriptAPIを介したプログラミング的アプローチをとるため、条件分岐・繰り返し処理が可能です。
この違いに着目し、単純なコマンド処理はfunctionで行い、複雑な条件分岐や繰り返し動作を含む処理をCustomCommandで実装するという使い分けがあるかもしれませんね。
引数を設定したい!
さて、以上で「単純なコマンド」の定義はできました。後はこれに追加機能を付けていくだけですね。そこで登場するのが、さっきスルーしたmandatoryParametersとoptionalParametersです。それぞれ「必須の引数」と「任意の引数」です。これを使えば、所謂ターゲットセレクターの設定なんかもできます。ただ、これについて話すと長くなるので続きは次回の記事で…。