Advancementで検知したEntityを特定する

:::note warn
初心者向けの記事ではありません。
:::


この記事は[Minecraft Command Advent Calendar 2024](https://qiita.com/advent-calendar/2024/mccmd)の18日目の記事です。
コマンド・データパックに関する基礎的な用語の解説はしていませんのでご注意を。

# 1. はじめに
データパックを作成している際に、`player_interacted_with_entity`などのトリガーを設定したadvancementで右クリックしたentityを特定したいと思ったことはないでしょうか？
今回はそれを正確に行える方法について解説します。
※1.19.4で追加されたexecuteのサブコマンド、[on](https://ja.minecraft.wiki/w/コマンド/execute#on)で代用可能な場面はそちらを推奨します。
<details open><summary>用語の解説</summary>

普段コマンドをやっていても見慣れない用語が幾つか出てくると思うのでその補足(外部サイトに丸投げ)。
[ビット(bit)](https://e-words.jp/w/ビット.html)　0か1のデータ。n個のbitは2^n個の情報を表すことができる。
[基数変換](https://nishisun.com/radix-conversion-decimal-number-binary-number-beginner/)　10進数を2進数に変換したりすること。
</details>

# 2. 処理の流れ
各entityに設定された被ることのないscoreを二進数に変換してtagに保存し、`advancement`で条件指定して、同じ組み合わせのtagを持つentityを探す。


:::note warn
bit数が増えると書くのが大変になったりラグも大きくなったりするので8~16bitを推奨します。
8bitは256までですがプレイヤーなどには使えます。
:::
# 3. 基数変換

:::note info
前提
entity_uuidというscoreは全entityの被ることなく設定され、範囲は1-65535(16bit)の間に制限されているとします。
基数変換の結果を保存するtagの名前はFindFlagN.M(例:FindFlag1.0)とします。
:::


方法は大きく2つあります。
<details><summary>1つ目</summary>

```text:radix_conversion.mcfunction
# scoreを破壊するためダミープレイヤーにコピー
    scoreboard players operation _ entity_id = @s entity_id
# 1bit
    execute if score _ entity_uuid matches 32768.. run tag @s add FindFlag0.0
    execute unless score _ entity_uuid matches 32768.. run tag @s add FindFlag0.1
    execute if score _ entity_uuid matches 32768.. run scoreboard players remove _ entity_id 32768
# 2bit
    execute if score _ entity_uuid matches 16384.. run tag @s add FindFlag1.0
    execute unless score _ entity_uuid matches 16384.. run tag @s add FindFlag1.1
    execute if score _ entity_uuid matches 16384.. run scoreboard players remove _ entity_id 16384
...
```
このような一般的な二分探索を用いた基数変換。

</details>

<details><summary>2つ目</summary>

```text:radix_conversion.mcfunction
# scoreを破壊するためダミープレイヤーにコピー
    scoreboard players operation _ entity_id = @s entity_id
# 65536(2^16)を掛けることで、32768以上ならオーバーフローしてマイナスになります
    scoreboard players operation _ entity_id *= $2^16 const
# オーバーフローしてたらtag追加
    execute if score _ entity_id matches 00.. run tag @s add FindFlag0.0
    execute if score _ entity_id matches ..-1 run tag @s add FindFlag0.1
# 0になるまでやる
    scoreboard players operation _ entity_id += _ entity_id
    execute if score _ entity_id matches 00.. run tag @s add FindFlag1.0
    execute if score _ entity_id matches ..-1 run tag @s add FindFlag1.1
```
このような算術オーバーフローを用いた基数変換。
</details>
基本的にどちらを使用しても構いません。
2の方が数値の変更が少なくて済むのでbit数が増えるほどおすすめです。

# 4. Advancement
entityの条件でnbtに`{Tags:[FindFlagN.M]}`を加え、それをbit数×2個書き連ねます。
そしてrequirementsで全種類のFindFlagが見つかっていた時のみ成功判定を出します
言葉で説明するよりも実際に見た方が早いと思うので下に例を乗せます。
<details><summary>例</summary>

```text:player_interacted_with_entity.json
// そのまま記載すると膨大な行数となるので一部省略。
{
  "criteria": {
    "0.0": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag0.0]}"
        }
      }
    },
    "0.1": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag0.1]}"
        }
      }
    },
    "1.0": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag1.0]}"
        }
      }
    },
    "1.1": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag1.1]}"
        }
      }
    },
    <省略>
    "15.0": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag15.0]}"
        }
      }
    },
    "15.1": {
      "trigger": "minecraft:player_interacted_with_entity",
      "conditions": {
        "entity": {
          "nbt": "{Tags:[FindFlag15.1]}"
        }
      }
    }
  },
  "requirements": [
    [
      "0.0",
      "0.1"
    ],
    [
      "1.0",
      "1.1"
    ],
    <省略>
    [
      "15.0",
      "15.1"
    ]
  ],
  "rewards": {
    "function": "entity_finder:find/"
  }
}
```

</details>

# 5. Advancementの達成状況を元にEntityを特定する
advancementの特定のcriteria(条件)が達成されているかは、セレクターやpredicateで検知できます。
それによってtagの組み合わせを取得して、同じ組み合わせを持つentityを探します。
方法は色々ありますが、1.20.2以降ならマクロを用いてtagを付ける方法(以下に例)がおすすめです。あとはそのtagが付いたentityを煮るなり焼くなりお好きにどうぞ(最後に外すのを忘れずに！)。
<details><summary>例(マクロ)</summary>

```text:entity_finder:find/.mcfunction
# まずは全部0に設定しておく
    data modify storage entity_finder: criteria set value {0:0b,1:0b,2:0b,3:0b,4:0b,5:0b,6:0b,7:0b,8:0b,9:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b}
# 0.1がtrueなら0を1にする
    execute if entity @s[advancement={entity_finder:player_interacted_with_entity={0.1=true}} run data modify storage entity_finder: criteria.0 set value 1
# 全てのbitで繰り返す
    execute if entity @s[advancement={entity_finder:player_interacted_with_entity={1.1=true}} run data modify storage entity_finder: criteria.1 set value 1
    ...

# マクロfunctionを呼び出し
    function entity_finder:find/macro with storage entity_finder
```
```text:entity_finder:find/macro.mcfunction
# マクロでセレクターにtagを入力する
    tag @e[tag=FindFlag15.$(15),tag=FindFlag14.$(14),tag=FindFlag13.$(13),tag=FindFlag12.$(12),tag=FindFlag11.$(11),tag=FindFlag10.$(10),tag=FindFlag9.$(9),tag=FindFlag8.$(8),tag=FindFlag7.$(7),tag=FindFlag6.$(6),tag=FindFlag5.$(5),tag=FindFlag4.$(4),tag=FindFlag3.$(3),tag=FindFlag2.$(2),tag=FindFlag1.$(1),tag=FindFlag0.$(0)] add Found

# 補足:tagの並びが降順なのはこの方が軽量化に繋がるため
```
</details>

# おわりに
初めての記事になるので、至らない点もあったかと存じます。それでもこの記事が役に立てたら幸いです。
[TheSkyBlessing](https://tsb.scriptarts.jp/)ではこの手法を使っているので参考にしてみてください([使っている部分のgithubのリンク](https://github.com/ProjectTSB/TheSkyBlessing/tree/master/TheSkyBlessing/data/mob_manager))。