スラッシュコマンドを実行する

ゲームテストフレームワークのスクリプトからもスラッシュコマンドを実行できます。

スラッシュコマンドに慣れている方は、細かいスクリプトの処理を覚える前に、スクリプト上でスラッシュコマンドを使えるようになっておくと便利です。

エンティティまたは次元のどちらかから実行する

スラッシュコマンドの使用は、エンティティ(Entity)、または次元(Dimension)が持っているrunCommand()メソッドを使って実行できます。

どちらを使うかで、結果が少し異なるので、適切な方を選ぶ必要があります。

とは言っても、初めのうちはあまり気にする必要はありません。 何かおかしいと思ったら、2種類の使い方があることを思い出し、あとで修正すればそれで十分です。

例文ではBeforeChatEventを使っているので、事前にイベントの使い方を確認しておいてください。

エンティティから実行した場合

そのエンティティ自身がコマンドを使ったことになります。 つまり、@sが有効になります。

下の例の場合は、メッセージの送信者がコマンドを使用したことになります。

import * as mc from 'mojang-minecraft';

export function runCommandByEntity(event: mc.BeforeChatEvent) {
  if (event.message === 'byentity') {
    // 使用するエンティティはメッセージの送信者
    const entity = event.sender;

    // 送信者自信がコマンドを使用したという扱いになるので
    // 雷はこのコマンドを実行したプレイヤーに落ちる
    entity.runCommand('summon lightning_bolt');
    // メッセージを送信すると送信者はプレイヤーの名前になる
    entity.runCommand(`say success`);

    event.cancel = true;
  }
}

mc.world.events.beforeChat.subscribe(runCommandByEntity);

次元から実行した場合とエラー処理

次元のメソッドからコマンドを実行した場合、@sを使うとエラーになります。

エラーが起こりうる場面ではtry...catch文 を使います。

try{}の中でエラーが起きた場合、そこでプログラム全体を停止させず、catch{}に移動してプログラムの処理を続けるという記法です。

import * as mc from 'mojang-minecraft';

export function runCommandByDimension(event: mc.BeforeChatEvent) {
  if (event.message === 'bydimension') {
    // 使用者はメッセージ送信者のいる次元(オーバーワールドやネザーのこと)
    const dimension = event.sender.dimension
    try {
      // 次元から@sを使うとエラー
      dimension.runCommand(`give @s apple`);
      // 上のコマンドが成功したら次のメッセージが表示されるはず
      // 実際は実行されずにcatch文に移動する
      dimension.runCommand(`say success`);
    } catch (error) {
      // エラーが起きたらこちらが表示される
      // 送信者は「システムメッセージ」になる
      dimension.runCommand(`say error: ${error}`);
      // -> {"statuCode": -2147352576, "statusMessage": "セレクターに合う対象がありません"}
    }

    event.cancel = true;
  }
}

mc.world.events.beforeChat.subscribe(runCommandByDimension);

エラーの内容は次のようになります。

次元自体はエンティティではないため、@sの対象が存在しないという単純なエラーです。

{
  "statuCode": -2147352576,
  "statusMessage": "セレクターに合う対象がありません"
}

エラー表示が必要なければ、catch部分は空にしてください。 この場合、エラーが起きても全体を停止させず、エラー表示も出さず、そのまま処理を続けます。

try {
  // プレイヤーのインベントリを空にする。ただし元から空だとエラー
  player.runCommand(`clear`);
} catch {
  // エラーの場合でも処理を止めずに続ける。エラー表示もしない。
}

コマンド結果を受け取る

コマンドを実行した後は、その実行結果をJSON構造のオブジェクトで受け取れます。

実行結果を受けて、さらに何かを行いたい場合に活用できそうです。

import * as mc from 'mojang-minecraft';

export function runCommandResult(event: mc.BeforeChatEvent) {
  if (event.message === 'commandresult') {
    try {
      // コマンドの結果をJSON構造のオブジェクトで受け取れる
      const result = event.sender.runCommand('effect @s levitation 3 1 true');
      // 内容をすべて表示。中に何が入っているかはコマンドにより異なる
      event.sender.runCommand(`say ${JSON.stringify(result)}`);
      // JSON構造なので、オブジェクトとして内容を取り出して使うことができる
      event.sender.runCommand(`say ${result.statusMessage}`);
      event.sender.runCommand(`say 強さ:${result.amplifier}, 秒数:${result.seconds}`);
    } catch (error) {
      event.sender.runCommand(`say error ${error}`);
    }

    event.cancel = true;
  }
}

mc.world.events.beforeChat.subscribe(runCommandResult);

取得内容は使用したコマンドによって変わります。

上の例の場合は/effectなので、強さ・効果時間・対象プレイヤーなどが入っています。

{
  "amplifier": 1,
  "effect": "levitation",
  "player": ["Steve"],
  "seconds": 3,
  "statusCode": 0,
  "statusMessage": "Steveに浮遊*1を3秒間与えました"
}

コマンド文にはテンプレートリテラル記法を使おう

文章の中に変数を埋め込みたい場合は「テンプレートリテラル」という記法を使うのがお勧めです。

次のどちらを使っても最終結果は同じですが、コードの見やすさが異なります。 状況に合わせて便利な方を使ってください。

文字列の結合

まず最初に旧来の記法を見てみましょう。 これは単純に文字列同士を+で結合しているだけです。

const target = 'Steve';
const item = 'apple';
const command = 'give ' + target + ' ' + item;
// -> give Steve apple

コマンドの場合は途中に半角スペースを入れる必要があるので、短文ですが結構ややこしいことになってしまっています。

こちらの方法では、正しくスペースを入れられずエラーになってしまいそうです。

テンプレートリテラル

次にテンプレートリテラルです。 この記法を使うには全体をバッククォート@ + Shiftで囲み、その中に${変数名}という記号を用いて変数を加えます。

const target = 'Steve';
const item = 'apple';
const command = `give ${target} ${item}`;
// -> give Steve apple

全体的に見やすくなり、コードも短くなりました。 どこにどれだけ半角スペースが入っているのかも分かりやすくなっています。

非同期で実行する

コマンドはrunCommandAsync()を使うと非同期に実行できます。 非同期とはものすごく簡単にいうと、すぐに実行せずに、他の処理が終わった後に余った時間で実行することです。

上で書いたような同期的な処理では、コマンドの実行が終わるまでMinecraft上の処理が停止します。 たまに処理落ちするのはこれが原因です。

一方、非同期処理では、ゲームのメイン処理の手が空いた時に実行するので、処理落ち的なことは起こりません。

基本的には同期処理であるrunCommand()を使えば問題ありませんが、何千・何万回もコマンドを実行するような使い方だとゲーム自体が停止し強制終了してしまいます。 筆者の環境では/setblockを1万5千回くらい連続で実行すると、Minecraft自体が強制終了しました。 このような場合には非同期処理で、メインの処理の合間に実行してやれば途中で停止させずにコマンドを実行できます。

import * as mc from 'mojang-minecraft';

export function runCommandAsync(event: mc.BeforeChatEvent) {
  if (event.message === 'async') {
    const player = event.sender;

    for (let i = 0; i < 3; i += 1) {
      // 非同期処理を登録する、この時点では実行されない
      player
        // この戻り値はPromise型
        .runCommandAsync(`say *** async: ${i + 1}`)
        // Promiseなので、.then()で繋げることができる
        // 結果は成功数だけ取得できる
        .then((result) => player.runCommandAsync(`say --- success(1): ${i + 1} / ${result.successCount}`))
        .then((result) => player.runCommandAsync(`say --- success(2): ${i + 1} / ${result.successCount}`))
        .then((result) => player.runCommandAsync(`say --- success(3): ${i + 1} / ${result.successCount}`))
        // エラー内容は取得できない
        .catch(() => player.runCommandAsync(`say error`));
    }

    // 最後に同期処理のコマンドを実行する
    for (let i = 0; i < 3; i += 1) {
      try {
        player.runCommand(`say +++ sync: ${i + 1}`);
      } catch {
      }
    }
  }
}

mc.world.events.beforeChat.subscribe(runCommandResult);

上の場合、次のように順番に実行・表示されます。

# メイン処理の中の同期処理が順番に実行される
+++ sync: 1
+++ sync: 2
+++ sync: 3
# BeforeChatイベントが終わったので送信したメッセージが表示される
async
# メイン処理が終わったので、非同期処理が実行される
*** async: 1
*** async: 2
*** async: 3
# 非同期処理が成功したので.then()で繋げた内容が実行される
--- success(1): 1
--- success(1): 2
--- success(1): 3
# 繋げた非同期処理が成功したので、さらに繋げた内容が実行される
--- success(2): 1
--- success(2): 2
--- success(2): 3
# 繋げた非同期処理が成功したので、さらに繋げた内容が実行される
--- success(3): 1
--- success(3): 2
--- success(3): 3

参考

次のページへ
スクリプトによる、はじめてのゲーム制作チュートリアル