この記事は、Minecraft Command Advent Calendar 2025の17日目の記事です。
はじめに
ブロックの設置位置を検知してコマンドを実行する仕組みを紹介します。
なお、書いてあるコマンドは1.21.10で動作するもので、繰り返しの多い部分などは省略しています。
何をするのか
ブロック設置時に、プレイヤーの付近で空気ブロックから別のブロックに置き換わった場所はプレイヤーがブロックを設置した場所である可能性が高いので、これを設置位置として検知します。
そのためにはプレイヤーの付近のブロックが空気であるかどうか、毎tick記録し続ける必要があります。
プレイヤーの付近のブロックを記録するために、32bit符号付き整数であるスコアボードの値のビットを真偽値の集まりとして扱います。それぞれのビットに位置を割り当て、「その位置に空気ブロックがあったか」の情報と結びつけます。
32bit符号付き整数
2進法では2倍された数値は元の数字の全体を一つ左にずらして右に0を加えた数値と一致します。
例えば13という数は2進法では
1101 と表されますが、これを2倍した26は
11010 と表されます。
10進法で10倍された数が、元の数字の右側に0を加えたと一致するのと同じですね。
2進法では右からn番目の位は2^(n-1)で、右から32番目のビットは2^31ですが、32bit符号付き整数では、右から32番目のビットは-2^31であり、このビットが立っていると数値は負になります。
このビットは2倍しても左にずれず、消滅します。
これらの性質を利用して、32bit符号付き整数で表現される数字の構成するビットを特定することができます。
まずは、一番左のビットを特定します。-2^31のビットが立っていれば数値が負になるので、もし負なら一番左のビットは1だと分かります。
その後は、数値を2倍すると数字が全体に1つ左へずれるため、同様にして1つずつ右のビットを特定することができます。
これを32回繰り返すことですべてのビットを特定できます。
例えば-1342177280という数は負なので、32bit符号付き整数として考えると一番左のビットは1であることが分かります。この数は32bit符号付き整数として2倍すると1610612736になり、これは正なので左から2番目のビットは0であることが分かります。
実際に、-1342177280は
10110000000000000000000000000000
というビット列で表現されます。
また、右からn番目のビットが1であるかどうかを知りたいときには、数値に2^(32-n)を掛けてから正負で判別することができます。
他にも、右からn番目のビットが0であるとき、2^(n-1)を足すことでそのビットを1にすることができます。
2^31を足したい場合、コマンドでは
#2^31を足す
scoreboard players add # _ 2147483648
などと書くとエラーになってしまうので、
scoreboard players set #2^31 _ 1073741824
scoreboard players add #2^31 _ 1073741824
scoreboard players operation # _ += #2^31 _
のようにあらかじめ2^31を用意しておくと良いです。
設置検知
プレイヤーのブロックを設置できる範囲は恐らく目の辺りから10*10*10くらいですが、キリが悪いのでここではプレイヤーの視線の2ブロック先の8*8*8の範囲を考えます。
8*8*8の範囲に含まれるブロックは512ブロックで、スコア1つ当たり32ビット使えるため、スコアは16個必要であることが分かります。
スコア1つで32ブロック分のデータを保存できるので、1つ当たり4*1*8の範囲を割り当てることにします。
1つ目を(-3,-3,-3)の位置から(0,-3,4)の範囲に割り当て、2つ目を(1,-3,-3)から(0,-3,4)の範囲に割り当てて、3つ目を1つ目の上、4つ目を2つ目の上、のように積み重ねて8*8*8の範囲をカバーします。
あとは512ブロックそれぞれが空気ブロックかどうかを記録するコマンドと、空気ブロック以外のブロックに変わった場所を検知するコマンドを書けばよいですね。
空気ブロックかどうかを記録するコマンドは、空気ブロックなら対応するビットの2の累乗を足してビットを書き込んでいくもので、空気ブロック以外のブロックに変わった場所を検知するコマンドは、スコアを2倍して1ビットずつデータを取り出し、設置時の配置と比較するものです。
ここでは気合いで512ブロックそれぞれに対応するコマンドを (出力するプログラムを) 書きました。
scoreboard objectives add _ dummy
scoreboard objectives add air_bitflag.00 dummy
scoreboard objectives add air_bitflag.40 dummy
scoreboard objectives add air_bitflag.01 dummy
scoreboard objectives add air_bitflag.41 dummy
scoreboard objectives add air_bitflag.02 dummy
scoreboard objectives add air_bitflag.42 dummy
scoreboard objectives add air_bitflag.03 dummy
scoreboard objectives add air_bitflag.43 dummy
scoreboard objectives add air_bitflag.04 dummy
scoreboard objectives add air_bitflag.44 dummy
scoreboard objectives add air_bitflag.05 dummy
scoreboard objectives add air_bitflag.45 dummy
scoreboard objectives add air_bitflag.06 dummy
scoreboard objectives add air_bitflag.46 dummy
scoreboard objectives add air_bitflag.07 dummy
scoreboard objectives add air_bitflag.47 dummy
execute positioned ^ ^ ^2 positioned ~0.5 ~0.5 ~0.5 align xyz run function advent:placement/memorize
# ビットを初期化する
scoreboard players set @s air_bitflag.00 0
scoreboard players set @s air_bitflag.40 0
scoreboard players set @s air_bitflag.01 0
...
scoreboard players set @s air_bitflag.46 0
scoreboard players set @s air_bitflag.07 0
scoreboard players set @s air_bitflag.47 0
# 位置を記録する
summon marker ~ ~ ~ {UUID:[I;0,0,0,1217]}
execute store result score @s placement.x run data get entity 0-0-0-0-4c1 Pos[0]
execute store result score @s placement.y run data get entity 0-0-0-0-4c1 Pos[1]
execute store result score @s placement.z run data get entity 0-0-0-0-4c1 Pos[2]
kill 0-0-0-0-4c1
# ビットを書き込む
execute if block ~-4 ~-4 ~-4 air run scoreboard players add @s air_bitflag.00 1
execute if block ~-4 ~-4 ~-3 air run scoreboard players add @s air_bitflag.00 2
execute if block ~-4 ~-4 ~-2 air run scoreboard players add @s air_bitflag.00 4
execute if block ~-4 ~-4 ~-1 air run scoreboard players add @s air_bitflag.00 8
...
execute if block ~-1 ~-4 ~2 air run scoreboard players add @s air_bitflag.00 1073741824
execute if block ~-1 ~-4 ~3 air run scoreboard players operation @s air_bitflag.00 += #2^31 _
execute if block ~0 ~-4 ~-4 air run scoreboard players add @s air_bitflag.40 1
execute if block ~0 ~-4 ~-3 air run scoreboard players add @s air_bitflag.40 2
...
execute if block ~3 ~3 ~0 air run scoreboard players add @s air_bitflag.47 268435456
execute if block ~3 ~3 ~1 air run scoreboard players add @s air_bitflag.47 536870912
execute if block ~3 ~3 ~2 air run scoreboard players add @s air_bitflag.47 1073741824
execute if block ~3 ~3 ~3 air run scoreboard players operation @s air_bitflag.47 += #2^31 _
{
"criteria": {
"main": {
"trigger": "minecraft:placed_block"
}
},
"rewards": {
"function": "advent:placement/placed_block"
}
}
advancement revoke @s only advent:placed_block
# 位置を復元する
summon marker ~ ~ ~ {UUID:[I;0,0,0,1217]}
execute store result entity 0-0-0-0-4c1 Pos[0] double 1 run scoreboard players get @s placement.x
execute store result entity 0-0-0-0-4c1 Pos[1] double 1 run scoreboard players get @s placement.y
execute store result entity 0-0-0-0-4c1 Pos[2] double 1 run scoreboard players get @s placement.z
execute at 0-0-0-0-4c1 align xyz run function advent:placement/check_placed
kill 0-0-0-0-4c1
# ビット列をコピーする (1tickに2回以上設置できることがある(?)ため)
scoreboard players operation # air_bitflag-00 = @s air_bitflag-00
scoreboard players operation # air_bitflag-40 = @s air_bitflag-40
scoreboard players operation # air_bitflag-01 = @s air_bitflag-01
...
scoreboard players operation # air_bitflag-46 = @s air_bitflag-46
scoreboard players operation # air_bitflag-07 = @s air_bitflag-07
scoreboard players operation # air_bitflag-47 = @s air_bitflag-47
# 直前まで空気で、現在空気じゃない場所でコマンドを実行
execute if score @s air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~3 unless block ~ ~ ~ air run function advent:placement/placed
# ビットを1つ左にずらす
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
execute if score # air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~2 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
execute if score # air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~1 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
execute if score # air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~0 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
...
execute if score # air_bitflag.07 matches ..-1 positioned ~-4 ~3 ~-1 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.07 += # air_bitflag.07
execute if score # air_bitflag.07 matches ..-1 positioned ~-4 ~3 ~-2 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.07 += # air_bitflag.07
execute if score # air_bitflag.07 matches ..-1 positioned ~-4 ~3 ~-3 unless block ~ ~ ~ air run function advent:placement/placed
scoreboard players operation # air_bitflag.07 += # air_bitflag.07
execute if score # air_bitflag.07 matches ..-1 positioned ~-4 ~3 ~-4 unless block ~ ~ ~ air run function advent:placement/placed
# 設置時の処理
空気ブロックではなくなった場所がプレイヤーの設置とは関係ない可能性があるため、精度を上げるために目線の先に一番近い変更箇所を設置場所として検知することにします。
また、破壊と設置を同時に行った場合には検知できないので、目線の先にブロックを設置したと判断するようにします。
# 変更箇所にマーカーを召喚する
execute if score # air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~3 unless block ~ ~ ~ air run summon marker ~ ~ ~ {Tags:[placement.replaced]}
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
execute if score # air_bitflag.40 matches ..-1 positioned ~3 ~-4 ~2 unless block ~ ~ ~ air run summon marker ~ ~ ~ {Tags:[placement.replaced]}
scoreboard players operation # air_bitflag.40 += # air_bitflag.40
...
# 7ブロック先まで目線の先を調べる
scoreboard players set #Loop _ 70
execute at @s anchored eyes positioned ^ ^ ^ run function advent:placement/ray
# 目線の先に一番近い変更箇所で実行する
execute at 0-0-0-0-4c1 align xyz at @e[type=marker,tag=placement.replaced,limit=1,sort=nearest,distance=..14] run function advent:placement/placed
# 変更箇所がなければ目線の先で実行する
execute unless entity @e[type=marker,tag=placement.replaced,limit=1,distance=..14] at 0-0-0-0-4c1 align xyz run function advent:player/placement/placed
kill 0-0-0-0-4c1
kill @e[type=marker,distance=..7,tag=placement.replaced]
tp 0-0-0-0-4c1 ~ ~ ~
scoreboard players remove #Loop _ 1
execute if block ~ ~ ~ air if score #Loop _ matches 1.. positioned ^ ^ ^0.1 run function advent:placement/ray
kill 0-0-0-0-4c1
# 設置時の処理
また、空気ブロック以外にも水や溶岩、草や雪などの置き換え可能ブロックもチェックの対象にすると良いでしょう。
まとめ
ブロックの設置を検知する方法を紹介しました。
この方法の弱点は、意図的に誤検知を発生される手段が数多くあり、設置時の負荷がまあまああり、平時の負荷もまあまああり、状況によっては設置座標を正しく検知することができないなど、いろいろあります。
しかし、それでもまあまあの精度があって設置するブロックに細工を施さなくても座標を検知できるのが便利なことが多いので場合によっては使えるかもです。
