初心者向けの記事ではありません。
この記事はMinecraft Command Advent Calendar 2024の18日目の記事です。
コマンド・データパックに関する基礎的な用語の解説はしていませんのでご注意を。
1. はじめに
データパックを作成している際に、player_interacted_with_entity
などのトリガーを設定したadvancementで右クリックしたentityを特定したいと思ったことはないでしょうか?
今回はそれを正確に行える方法について解説します。
※1.19.4で追加されたexecuteのサブコマンド、onで代用可能な場面はそちらを推奨します。
用語の解説
普段コマンドをやっていても見慣れない用語が幾つか出てくると思うのでその補足(外部サイトに丸投げ)。
ビット(bit) 0か1のデータ。n個のbitは2^n個の情報を表すことができる。
基数変換 10進数を2進数に変換したりすること。
2. 処理の流れ
各entityに設定された被ることのないscoreを二進数に変換してtagに保存し、advancement
で条件指定して、同じ組み合わせのtagを持つentityを探す。
bit数が増えると書くのが大変になったりラグも大きくなったりするので8~16bitを推奨します。
8bitは256までですがプレイヤーなどには使えます。
3. 基数変換
前提
entity_uuidというscoreは全entityの被ることなく設定され、範囲は1-65535(16bit)の間に制限されているとします。
基数変換の結果を保存するtagの名前はFindFlagN.M(例:FindFlag1.0)とします。
方法は大きく2つあります。
1つ目
# 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
...
このような一般的な二分探索を用いた基数変換。
2つ目
# 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
このような算術オーバーフローを用いた基数変換。
4. Advancement
entityの条件でnbtに{Tags:[FindFlagN.M]}
を加え、それをbit数×2個書き連ねます。
そしてrequirementsで全種類のFindFlagが見つかっていた時のみ成功判定を出します
言葉で説明するよりも実際に見た方が早いと思うので下に例を乗せます。
例
// そのまま記載すると膨大な行数となるので一部省略。
{
"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/"
}
}
5. Advancementの達成状況を元にEntityを特定する
advancementの特定のcriteria(条件)が達成されているかは、セレクターやpredicateで検知できます。
それによってtagの組み合わせを取得して、同じ組み合わせを持つentityを探します。
方法は色々ありますが、1.20.2以降ならマクロを用いてtagを付ける方法(以下に例)がおすすめです。あとはそのtagが付いたentityを煮るなり焼くなりお好きにどうぞ(最後に外すのを忘れずに!)。
例(マクロ)
# まずは全部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
# マクロでセレクターに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の並びが降順なのはこの方が軽量化に繋がるため
おわりに
初めての記事になるので、至らない点もあったかと存じます。それでもこの記事が役に立てたら幸いです。
TheSkyBlessingではこの手法を使っているので参考にしてみてください(使っている部分のgithubのリンク)。