【スタンドの弓矢】

はじめに

今回の実装アイテムはジ〇ジョネタです。
以下のように「スタンドの弓」で矢を放つと...


矢を放った場所にスタンドが現れます。知る人ぞ知るエージェント君ですw

このスタンド召喚の仕組みは矢のアイテムシリーズである「いなずまの矢」「はかいの矢」「機雷の弓」のページでご紹介したものを応用しています。

ジ〇ジョの作中の使い方とは異なりますが「スタンドの弓矢」を使うと以下の2つの役割を持たせる事ができます。
1)荷物の輸送
2)スタンド攻撃
それでは順を追って使い方を説明していきます。

荷物の輸送

エージェントにはインベントリが存在しますので右クリックすると以下のようにインベントリが開きます。


アイテムを取り出したり格納したりする時には「スタンドの弓」を使ってスタンドを好きな位置に召喚してから使います。

一度スタンドを召喚すると以下のように「ネザー」や「エンド」でもついてきてくれますので、あらゆるところで召喚できるエンダーチェストのように使う事ができます。

エージェントは無敵なので他のモブに倒される心配はありません。
常に浮いているので奈落に落ちる心配もありません。
溶岩に浸かってもダメージを受けません。



※「いなずまの矢」や「はかいの矢」などの特殊機能が付与されている矢は、それぞれの機能が発動してしまうので使えません。

「スタンドの弓」の取得

本環境のビヘイビアパック/リソースパックなどのアドオンパックを適用しておけば、以下の方法でユニークアイテムとして「スタンドの弓」が手に入ります。

ショップで購入する場合

以下のショップで購入できます。
▶ネットショップ

コマンドで取得する場合

以下はワールドオーナーやシステム組み込み用としてコマンドで取得する方法です。



スタンド攻撃

弓は何を使っても構いませんが、スタンドを攻撃モードにするには「スタンドの矢」が必要です。

この矢を以下のように攻撃対象へ放つと地中を移動しながらスタンドが攻撃を始め、相手がデスポーンするまで継続します。


矢は攻撃対象へ命中しなくても以下のように近くを横切るだけでも発動します。



射程距離は10ブロックですが攻撃中にプレイヤーからの射程距離から外れると攻撃を停止します。その場合は再度近づいてから矢を放ってください。

「スタンドの矢」の取得

本環境のビヘイビアパック/リソースパックなどのアドオンパックを適用しておけば、以下の方法でユニークアイテムとして「スタンドの矢」が手に入ります。

ショップで購入する場合

以下のショップで購入できます。
▶ネットショップ

コマンドで取得する場合

以下はワールドオーナーやシステム組み込み用としてコマンドで取得する方法です。



サーバー側の実装

他の弓矢のアイテムと同様に"ItemUsed"のサブスクライブイベントを使用しています。

※サブスクライブイベントの処理内容については>> こちらでご紹介しています。

スタンド召喚時の仕組みは「いなずまの矢」や「はかいの矢」とほぼ変わりません。最後にsummonコマンドで雷やエンドクリスタルを呼び出していたところをエージェントの移動コマンドに置き換えただけです。

また、スタンド攻撃時も含めて今回はWebsocketサーバーでしか使う事ができない以下の隠しコマンドを使用しています。
隠しコマンド
> agent tp <coordinates>              エージェントの移動
> agent attack <direction>            エージェントによる攻撃
> querytarget <target selectors>      ターゲットエンティティの状態を取得する
                    

スタンド攻撃時は大まかに分けると以下の処理フローで実装しています。
1)攻撃対象のモブに対して「スタンドの矢」を放つ
2)攻撃対象のモブに対してタグを付与する
3)攻撃対象のタグを持っているモブに対してエージェントを移動して攻撃をしかける
4)攻撃対象のタグを持っているモブがデスポーンしているか検査する
上記4)の検査でデスポーンしていなければ3)以降を繰り返しています。
上記2)でタグを付与する時には攻撃対象を特定するために座標計算用の矢をスポーンさせていますが、その計算ロジックは「いなずまの弓」を作った時と全く同じものを使っています。

キューとステータスUNITの登録


イベントを処理するための任意のコマンド名を以下のファイルへ定義します。
今回追加しているのはスタンド攻撃時のレスポンスを受信するためのものです。
app/CommandUnits/CommandQueueEnumForMinecraft.php
case RESPONSE_STAND_ATTACK = 'response_stand_attack';
                    

コマンド名を以下の場所へ追加して利用可能にします。
app/CommandUnits/CommandForMinecraft.php
protected const QUEUE_LIST = [
    CommandQueueEnumForMinecraft::RESPONSE_STAND_ATTACK->value
];
                    

コマンド名と処理(関数)の関係を以下のメソッドへ追加して紐づけを行います。
app/CommandUnits/CommandForMinecraft.php
public function getUnitList(string $p_que): array
{
    $ret = [];
    ・
    ・
    ・
    if($p_que === CommandQueueEnumForMinecraft::RESPONSE_STAND_ATTACK->value)
    {
        $ret[] = [
            'status' => CommandStatusEnumForMinecraft::START->value,
            'unit' => $this->getResponseStandAttackStart()
        ];
    }
    ・
    ・
    ・
    return $ret;
}
                    

新規実装箇所


受信したイベントデータをコマンド名へ変換する処理を以下のコマンドディスパッチャーへ追加します。
以下の黄色の部分が今回追加したコマンドです。
app/InitClass/InitForMinecraft.php
public function getCommandDispatcher()
{
    return function(ParameterForMinecraft $p_param, $p_dat): ?string
    {
        $minecraft = $p_param->isMinecraft();
        if($minecraft === true)
        {
            ・
            ・
            ・
            if(isset($p_dat['data']['body']['statusCode']))
            {
                $sta = $p_param->getStatusName();
                if($sta !== null)
                {
                    return null;
                }

                $rid = $p_param->getAwaitResponseForCustomize('stand-attack');
                if($rid === null)
                {
                    return CommandQueueEnumForMinecraft::RESPONSE->value;
                }

                return CommandQueueEnumForMinecraft::RESPONSE_STAND_ATTACK->value;

            }
            ・
            ・
            ・
        }
    }
}
                    

「スタンドの弓」「スタンドの矢」を使用した時の判定処理を以下の場所に実装しています。
以下の黄色の部分が今回追加した処理です。
app/CommandUnits/CommandForMinecraft.php
protected function getItemUsedArrow()
{
    return function(ParameterForMinecraft $p_param): ?string
    {
        ・
        ・
        ・

        // スタンドの召喚
        if($bow_type['bow_type'] === 461 && $rcv['data']['body']['item']['aux'] === 0)
        {
            // コマンド送信(スタンド召喚)
            $cmd_data = $p_param->getCommandDataForStandSummon($rcv['data']['body']['player']['name']);
            $data =
            [
                'data' => $cmd_data
            ];
            $p_param->setSendStack($data);
            return null;
        }
        // スタンドによる攻撃
        if($rcv['data']['body']['item']['aux'] === 471)
        {
            // 相対座標の取得
            $x = $rcv['data']['body']['player']['position']['x'];
            $y = $rcv['data']['body']['player']['position']['y'];
            $z = $rcv['data']['body']['player']['position']['z'];
            $yrot = $rcv['data']['body']['player']['yRot'];
            $p_param->getRelativeCoordinates($x, $y, $z, $yrot, 5);

            // コマンド送信(座標計算の矢をスポーン)
            $cmd_data = $p_param->getCommandDataForStandArrowSpawn($rcv['data']['body']['player']['name'], $x, $y, $z);
            $data =
            [
                'data' => $cmd_data
            ];
            $p_param->setSendStack($data);

            // コマンド送信(矢へのタグの付与)
            $cmd_data = $p_param->getCommandDataForStandArrowTag($rcv['data']['body']['player']['name'], $x, $y, $z);
            $data =
            [
                'data' => $cmd_data
            ];
            $p_param->setSendStack($data);

            // コマンド送信(攻撃相手へのタグ付与)
            $cmd_data = $p_param->getCommandDataForStandAttackTag($rcv['data']['body']['player']['name']);
            $data =
            [
                'data' => $cmd_data
            ];
            $p_param->setSendStack($data);

            // コマンド送信(スタンド攻撃)
            $p_param->sendCommandDataForStandAttack();
            return null;
        }
        else

        ・
        ・
        ・
    }
}
                    

上記でご覧のようにスタンド攻撃を実行するために発行しているコマンドは複数あります。
攻撃対象を特定するために座標計算用の矢をスポーンさせてからその場所を基準に相手を特定しているので少し複雑になっています。

これらの位置関係を図で表すと以下のようになります。


上記の青色の円の内側が攻撃対象範囲になります。
そしてプレイヤーから見て半径5ブロック(直径10ブロック)の円の端までが射程距離となります。

さらにRESPONSE_STAND_ATTACKコマンド名に紐づけた以下の処理(関数)を実装する事で「攻撃⇒デスポーン検査」の処理を実施しています。
app/CommandUnits/CommandForMinecraft.php
protected function getResponseStandAttackStart()
{
    return function(ParameterForMinecraft $p_param): ?string
    {
        $p_param->logWriter('debug', ['MINECRAFT RESPONSE_STAND_ATTACK:START' => 'START']);

        $rcv = $p_param->getRecvData();
        $w_ret = $p_param->getAwaitResponseForCustomize('stand-attack');
        if($w_ret === null)
        {
            return null;
        }
        if($w_ret === $rcv['data']['header']['requestId'])
        {
            // ユーザー名重複時のレスポンス
            if($rcv['data']['body']['statusMessage'] === 'セレクターに合う対象がありません')
            {
                $p_param->setAwaitResponseForCustomize('stand-attack', null);
                return null;
            }
            else
            {
                // コマンド送信(スタンド攻撃)
                $p_param->sendCommandDataForStandAttack();
            }
        }

        return null;
    };
}
                    

※攻撃対象のモブがいない時「セレクターに合う対象がありません」という文字列で判定していますが今後適切なパラメータが見つかれば変更する可能性があります。

おわりに

エージェントはエンティティとして認識されないばかりかWebsocketサーバーからのみコントロールできるものなので扱いが少し複雑です。
functionコマンドでの利用はできますがWebsocketサーバーがトリガーになっていないリピート利用や再帰コールも不可能です。
そのためスタンド攻撃で相手がデスポーンした事を検出するために隠しコマンドであるquerytargetを利用しています。

いずれにしてもワールド負荷に直結するリピート利用は極力避けるようにしているので結果オーライといったところです。