damageコマンド以前のダメージの与え方

この記事は、[Minecraft Command Advent Calendar 2024](https://qiita.com/advent-calendar/2024/mccmd)の14日目の記事です。

# はじめに
/damageコマンドが実装される以前に考案された、モブやプレイヤーにダメージを与える技術をいくつか紹介します。

なお、見出しにはその技術が使える最も古いバージョンを記載しており、コマンド例はそのバージョンでの例です。また、コマンド例は2回以上実行することを考慮していないものなど、実用には不十分なものが多いです。
## 1．instant_damage 1.5(13w09b)～
effectコマンドで即時ダメージや、アンデットのモブには即時回復を付与することでダメージを与える方法です。

この方法では与えるダメージ量をエフェクトのレベルでしか指定できず、防御力も貫通するため少々不便でした。
```plaintext:コマンド例
effect give @e[name=victim] instant_damage 1 0 true
```

## 2．FallDistanceをいじる 1.8(14w32b)～
FallDistanceをいじることで、接地しているモブに強制的に落下ダメージを与えます。
接地していないモブや落下ダメージが無効なモブ、プレーヤには効果がありませんが、1コマンドで使える上、アンデット系のモブにも効果があるというところで、使われることがありました。

ダメージ量はFallDistanceから3を引いた値です。
```plaintext:コマンド例
entitydata @e[name=victim] {FallDistance:8f}
```

## 3．Johnnyのテレポート 1.14(19w08a)～1.21.2(24w40a)
これはあまり知られていない（気がする）バグの一つですが、何かに乗っているエンティティをテレポートで移動させると、実体だけが移動し見た目には反映されないという[バグ](https://bugs.mojang.com/browse/MC-64265)がありました。
また、19w08aから、Johnny化したヴィンディケーターは召喚後、必ずノータイムで敵を攻撃するようになりました。（[@Mononobe氏の記事で触れられていますね](https://qiita.com/Mononobe/items/53369aeb00ef88254570)）

これらを利用して、Johnny化したヴィンディケーターを何かに乗せ、それを対象へテレポートさせることで、透明なヴィンディケーターに対象を攻撃させることができます。

この方法は、ダメージがヴィンディケーターの攻撃によるものなので、防御力は貫通しませんし、持ち物によって火属性などのエンチャント効果、斧での盾破壊の効果などを付与することができます。

このヴィンディケーターはJohnnyなのでプレイヤー以外のモブにもダメージを与えることができますが、ヴィンディケーターに対してはダメージを与えることができません。また、子供の村人やガストなど、Johnnyなヴィンディケーターが攻撃しないバグのあるモブにも無効です。

```plaintext:コマンド例
summon armor_stand ~ ~ ~ {Passengers:[{id:"vindicator",Johnny:1b,Tags:["johnny"]}]}
tp @e[tag=johnny] @e[limit=1,tag=victim]
```
▼実行結果(19w08aにて)
![Johnnyのテレポート.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3593091/f44421c1-d876-9b17-be57-026a25ca3c0c.gif)

## 4．ダメージの分だけ体力を減らす 1.13(17w45b)～
体力をスコアを経由して減らし、その後にダメージ演出を発生させる方法です。
スコアを経由するので与えるダメージ量を細かく調節することができ、ダメージ量を防御力などで減衰させれば、疑似的に非防御力貫通のダメージを与えることができます。また、体力が0になった時の処理もカスタマイズできるため、damageコマンド追加以前の最強の方法でした。

この方法では、体力の減らし方にプレイヤーとモブで違いがあります。

まず、モブの場合ではexecute storeで体力をスコアに代入し、スコアを減らしてから、execute storeで逆にスコアを体力に代入します。この方法は特にデメリットもないため、どのバージョンでも使われています。

```plaintext:コマンド例
execute store result score # health run data get entity @e[tag=victim,limit=1] Health 100
scoreboard players remove # health 500
execute store result entity @e[tag=victim,limit=1] Health float 0.01 run scoreboard players get # health
```
プレイヤーの場合は、まず体力の代わりに最大体力を減らします。
体力が最大体力を超過している状態で回復すると、体力が最大体力以下に制限されます。
そのため、
① 最大体力を(体力-ダメージ)まで減らす
② 回復する
③ 回復して制限されるまで1tick待つ
④ 最大体力を元に戻す
というような流れとなります。
最大体力の変更の方法は2あり、ここでは2つ紹介します。
### 4-1．lootで最大体力をいじる 1.14(18w43a)～
18w43aではloot(drop)コマンドが追加されました。
ブロックを破壊したときや敵を倒したときにドロップするアイテムなどを、プレイヤーやブロックのインベントリに入れることができます。
インベントリにアイテムを入れることができるというところで、replaceitemと似ていますが、このコマンドがreplaceitemと違う点は、loot_tables(loot_table)を利用して入れるアイテムを動的に変えられる点です。

ここでは、AttributeModifiersの付いた装備で最大体力を変更します。
loot_tablesでシュルカーボックスを中身のみドロップするようにすることで、AttributeModifiersの値を変更したアイテムをプレイヤーに装着できるようにしています。（[@crops121氏の記事で詳しく紹介されています](https://qiita.com/crops121/items/ddc5dfdcf25906d248a0)）
```json:minecraft/black_shulker_box.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:dynamic",
              "name": "minecraft:contents"
            }
          ]
        }
      ]
    }
  ]
}

```
<details><summary>(特定のアイテムのみで中身をドロップする版)</summary>

```json:minecraft/black_shulker_box.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:dynamic",
              "name": "minecraft:contents",
              "conditions": [
                {
                  "condition": "minecraft:match_tool",
                  "predicate": {
                    "item": "minecraft:debug_stick"
                  }
                }
              ]
            },
            {
              "type": "minecraft:item",
              "name": "minecraft:black_shulker_box",
              "functions": [
                {
                  "function": "minecraft:copy_name",
                  "source": "block_entity"
                },
                {
                  "function": "minecraft:set_contents",
                  "entries": [
                    {
                      "type": "minecraft:dynamic",
                      "name": "minecraft:contents"
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

```
</details>

```plaintext:コマンド例(事前に実行)
//プレイヤーに装着するアイテムとそれを入れるシュルカーボックスを用意する
setblock 0 0 0 black_shulker_box
replaceitem block 0 0 0 container.0 carrot{AttributeModifiers:[{AttributeName:"generic.maxHealth",Name:"generic.maxHealth",UUIDLeast:1,UUIDMost:2,Slot:"head"}]}

//プレイヤーが装備していたアイテムを保存するシュルカーボックスを用意する
setblock 1 0 0 black_shulker_box
```

```plaintext:コマンド例
//プレイヤーに装着するアイテムの最大体力の増減量を調節する
execute store result score # health run data get entity @a[tag=victim,limit=1] Health 100
scoreboard players remove # health 500
scoreboard players remove # health 2000 //最大体力を引く
execute store result block 0 0 0 Items[0].tag.AttributeModifiers[0].Amount float 0.01 run scoreboard players get # health

//プレイヤーが装備していたアイテムを保存する
replaceitem block 1 0 0 container.0 stone
data modify block 1 0 0 Items[0].id set from entity @a[tag=victim,limit=1] Inventory[{Slot:103b}].id
data modify block 1 0 0 Items[0].tag set from entity @a[tag=victim,limit=1] Inventory[{Slot:103b}].tag
execute store result block 1 0 0 Items[0].Count int 1 run data get entity @s Inventory[{Slot:103b}].Count

//プレイヤーにアイテムを装着し、即時回復を付与することで、体力が更新され最大体力と同じになる
drop entity @a[tag=victim,limit=1] armor.head mine 0 0 0 debug_stick
effect give @a[tag=victim,limit=1] instant_health 1 31 true
```

```plaintext:コマンド例(1tick後に実行)
//頭装備を元に戻す
drop entity @a[tag=victim,limit=1] armor.head mine 1 0 0 debug_stick
```

### 4-2．attributeを使う 1.16(20w17a)～
attributeコマンドは最大体力などattributeの値を変更することができます。
定数での加算や乗算しかありませんが、スコアの値を代入する方法は様々あります。

```
execute if score # health matches 1 run attribute @s generic.max_health base set 0.01
execute if score # health matches 2 run attribute @s generic.max_health base set 0.02
execute if score # health matches 3 run attribute @s generic.max_health base set 0.03
execute if score # health matches 4 run attribute @s generic.max_health base set 0.04
...
```
などという方法でも構いませんが、これを数千行や数十万行も並べるというのは、どう考えても良くないので他の方法を使いましょう。
ここでは設定したい値を2進数で表して、ビットの立っている桁の値をmodifireで加算しています。

```plaintext:コマンド例(victimを@sとして実行)
execute store result score # health run data get entity @s Health 100
execute store result score #max health run attribute @s generic.max_health get 100

scoreboard players remove # health 500
scoreboard players operation # health -= #max health
scoreboard players operation #_ health = # health

attribute @s generic.max_health modifier remove 0-0-0-0-0
attribute @s generic.max_health modifier remove 0-0-0-0-1
attribute @s generic.max_health modifier remove 0-0-0-0-2
attribute @s generic.max_health modifier remove 0-0-0-0-3
attribute @s generic.max_health modifier remove 0-0-0-0-4
attribute @s generic.max_health modifier remove 0-0-0-0-5
attribute @s generic.max_health modifier remove 0-0-0-0-6
attribute @s generic.max_health modifier remove 0-0-0-0-7
attribute @s generic.max_health modifier remove 0-0-0-0-8
attribute @s generic.max_health modifier remove 0-0-0-0-9
attribute @s generic.max_health modifier remove 0-0-0-0-10
attribute @s generic.max_health modifier remove 0-0-0-0-11
attribute @s generic.max_health modifier remove 0-0-0-0-12
attribute @s generic.max_health modifier remove 0-0-0-0-13
attribute @s generic.max_health modifier remove 0-0-0-0-14
attribute @s generic.max_health modifier remove 0-0-0-0-15
attribute @s generic.max_health modifier remove 0-0-0-0-16

execute if score #_ health matches ..-65536 run attribute @s generic.max_health modifier add 0-0-0-0-0 binary0 -655.36 add
execute if score #_ health matches ..-65536 run scoreboard players add #_ health 65536

execute if score #_ health matches ..-32768 run attribute @s generic.max_health modifier add 0-0-0-0-1 binary1 -327.68 add
execute if score #_ health matches ..-32768 run scoreboard players add #_ health 32768

execute if score #_ health matches ..-16384 run attribute @s generic.max_health modifier add 0-0-0-0-2 binary2 -163.84 add
execute if score #_ health matches ..-16384 run scoreboard players add #_ health 16384

execute if score #_ health matches ..-8192 run attribute @s generic.max_health modifier add 0-0-0-0-3 binary3 -81.92 add
execute if score #_ health matches ..-8192 run scoreboard players add #_ health 8192

execute if score #_ health matches ..-4096 run attribute @s generic.max_health modifier add 0-0-0-0-4 binary4 -40.96 add
execute if score #_ health matches ..-4096 run scoreboard players add #_ health 4096

execute if score #_ health matches ..-2048 run attribute @s generic.max_health modifier add 0-0-0-0-5 binary5 -20.48 add
execute if score #_ health matches ..-2048 run scoreboard players add #_ health 2048

execute if score #_ health matches ..-1024 run attribute @s generic.max_health modifier add 0-0-0-0-6 binary6 -10.24 add
execute if score #_ health matches ..-1024 run scoreboard players add #_ health 1024

execute if score #_ health matches ..-512 run attribute @s generic.max_health modifier add 0-0-0-0-7 binary7 -5.12 add
execute if score #_ health matches ..-512 run scoreboard players add #_ health 512

execute if score #_ health matches ..-256 run attribute @s generic.max_health modifier add 0-0-0-0-8 binary8 -2.56 add
execute if score #_ health matches ..-256 run scoreboard players add #_ health 256

execute if score #_ health matches ..-128 run attribute @s generic.max_health modifier add 0-0-0-0-9 binary9 -1.28 add
execute if score #_ health matches ..-128 run scoreboard players add #_ health 128

execute if score #_ health matches ..-64 run attribute @s generic.max_health modifier add 0-0-0-0-10 binary10 -0.64 add
execute if score #_ health matches ..-64 run scoreboard players add #_ health 64

execute if score #_ health matches ..-32 run attribute @s generic.max_health modifier add 0-0-0-0-11 binary11 -0.32 add
execute if score #_ health matches ..-32 run scoreboard players add #_ health 32

execute if score #_ health matches ..-16 run attribute @s generic.max_health modifier add 0-0-0-0-12 binary12 -0.16 add
execute if score #_ health matches ..-16 run scoreboard players add #_ health 16

execute if score #_ health matches ..-8 run attribute @s generic.max_health modifier add 0-0-0-0-13 binary13 -0.08 add
execute if score #_ health matches ..-8 run scoreboard players add #_ health 8

execute if score #_ health matches ..-4 run attribute @s generic.max_health modifier add 0-0-0-0-14 binary14 -0.04 add
execute if score #_ health matches ..-4 run scoreboard players add #_ health 4

execute if score #_ health matches ..-2 run attribute @s generic.max_health modifier add 0-0-0-0-15 binary15 -0.02 add
execute if score #_ health matches ..-2 run scoreboard players add #_ health 2

execute if score #_ health matches ..-1 run attribute @s generic.max_health modifier add 0-0-0-0-16 binary16 -0.01 add

effect give @s instant_health 1 31 true
```

```plaintext:コマンド例(1tick後に実行、victimを@sとして実行)
attribute @s generic.max_health modifier remove 0-0-0-0-0
attribute @s generic.max_health modifier remove 0-0-0-0-1
attribute @s generic.max_health modifier remove 0-0-0-0-2
attribute @s generic.max_health modifier remove 0-0-0-0-3
attribute @s generic.max_health modifier remove 0-0-0-0-4
attribute @s generic.max_health modifier remove 0-0-0-0-5
attribute @s generic.max_health modifier remove 0-0-0-0-6
attribute @s generic.max_health modifier remove 0-0-0-0-7
attribute @s generic.max_health modifier remove 0-0-0-0-8
attribute @s generic.max_health modifier remove 0-0-0-0-9
attribute @s generic.max_health modifier remove 0-0-0-0-10
attribute @s generic.max_health modifier remove 0-0-0-0-11
attribute @s generic.max_health modifier remove 0-0-0-0-12
attribute @s generic.max_health modifier remove 0-0-0-0-13
attribute @s generic.max_health modifier remove 0-0-0-0-14
attribute @s generic.max_health modifier remove 0-0-0-0-15
attribute @s generic.max_health modifier remove 0-0-0-0-16
```

### 4-3．ダメージ演出を発生させたり死亡させたりする
これまでの内容には、モブの場合はダメージ演出や死亡時のアイテムドロップがない、プレイヤーの場合は体力が0以下になった時に[バグってリスポーン出来なくなる](https://bugs.mojang.com/browse/MC-92633)などといった問題があります。

まずダメージ演出は、即時ダメージや即時回復を使う気がします。

effectコマンドで即時ダメージ29～30を与えたときにダメージ演出だけが発生するのはよく知られていますが、これは即時回復29のポーションを投げつけられた時に問答無用で死ぬのと同じ原理です。
これらはどちらも、効果量がオーバフローしてマイナスになっており、回復の場合は体力にマイナスが加算され、ダメージの場合はマイナスのダメージがカットされてダメージを受けていないということになります。

```plaintext:コマンド例
effect give @e[tag=victim,limit=1] instant_damage 29 0 true
effect give @e[tag=victim,limit=1] instant_health 29 0 true
```
しかしながら、この方法にはバグがあり、この方法でのダメージ演出中に他のダメージを受けると即死します。これは、ダメージ演出中に元のダメージより高いダメージを受けたときに、後のダメージを適応する仕様が原因です。
後のダメージ-元のダメージ 分のダメージを与えるため、元のダメージにマイナスの値が入っていると、その分ダメージが増えます。

これを回避するために、AECを用いて1tickだけ耐性を付与するらしいです。このとき、Effectsに耐性->即時ダメージ(回復)の順番で記述することがミソらしいです。
```plaintext:コマンド例
execute at @e[tag=victim,limit=1,type=!namespace:undead] run summon area_effect_cloud ~ ~ ~ {Duration:1,Age:-1,Effects:[{Id:11b,Amplifier:4b,Duration:1,ShowParticles:0b},{Id:7b,Amplifier:0b,Duration:1,ShowParticles:0b}]}
execute at @e[tag=victim,limit=1,type=namespace:undead] run summon area_effect_cloud ~ ~ ~ {Duration:1,Age:-1,Effects:[{Id:11b,Amplifier:4b,Duration:1,ShowParticles:0b},{Id:6b,Amplifier:0b,Duration:1,ShowParticles:0b}]}
```

また、誰かから誰かへの攻撃の場合、敵対させたりするために雪玉やAreaEffectCloudでダメージ演出を発生させることもできます。
```plaintext:コマンド例
execute at @e[tag=victim,limit=1,type=!namespace:undead] run summon area_effect_cloud ~ ~ ~ {Tags:["targeting"],Duration:1,Age:-1,Effects:[{Id:11b,Amplifier:4b,Duration:1,ShowParticles:0b},{Id:7b,Amplifier:0b,Duration:1,ShowParticles:0b}]}
execute at @e[tag=victim,limit=1,type=namespace:undead] run summon area_effect_cloud ~ ~ ~ {Tags:["targeting"],Duration:1,Age:-1,Effects:[{Id:11b,Amplifier:4b,Duration:1,ShowParticles:0b},{Id:6b,Amplifier:0b,Duration:1,ShowParticles:0b}]}
data modify entity @e[tag=targeting,limit=1] Owner set from entity @e[tag=attacker,limit=1] UUID
```


死亡時はkillコマンドで処理します。
```plaintext:コマンド例
execute if score # health matches ..0 run kill @e[tag=victim,limit=1]
```

プレイヤーの場合は死亡時のログを変えることもできます。
```plaintext:コマンド例(victimを@sとして実行)
//本物の死亡ログを隠す
gamerule showDeathMessages false

kill @s
tellraw @a [{"selector":"@s"},"は殺された"]
gamerule showDeathMessages true
```

### 4-4．エンダードラゴンについて
エンダードラゴンはその特異な仕様から、数々のデータパックのコード行数を増やしてきました。そのような仕様の中でダメージを与えることに関係するものをいくつか紹介します。

#### 4-4-1．即時ダメージとか無効
エンダードラゴンにはエフェクトが効きません。そのため、ダメージ演出を発生させることができません。大変ですね。

#### 4-4-2．killでは発動しない死亡演出
エンダードラゴンはkillコマンドを用いて殺したときに死亡演出が発生せず、消滅します。Healthを0に設定すると死亡演出が発生し、紫色の光を出して崩壊しますが、本来はポータルまで飛んでから崩壊します。
DragonPhaseを9に設定すると、正しい死亡演出が発生します。

#### 4-4-3．9つに分かれた胴体
F3+Bなどで当たり判定を確認すると緑色で当たり判定がいくつも表示されていることが分かりますが、これらが別々のエンティティとして扱われるという[バグ](https://bugs.mojang.com/browse/MC-146503)がありました。

▼エンダードラゴンのヒットボックス
![エンドラ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3593091/46c8f2bf-8aaf-9436-0d7b-33d70c1c02e5.png)

```
execute if entity @e[type=ender_dragon]
kill @e[type=ender_dragon]
```
などと実行すると、それぞれ
```
テストに成功しました。回数:9
9体のエンティティをキルしました
```
と表示されることから分かります。

この9体の中で8体は当たり判定用に存在しており、HealthやDragonPhaseといった情報はそのうち1体にしか入っていないのです。そのため、対象を誤るとダメージを与えることができません。

ではどうすれば良いのかというと、当たり判定用のドラゴンがほとんどデータタグを持っていないことを利用します。当たり判定用のドラゴンはTeamの情報も持っていないため、
```
@e[team=!qawsedrftgyhujikolp]
```
といった、適当なチームを除外することで、Teamの情報を持っていないドラゴンを除外することができます。

# まとめ
damageコマンド以前のダメージの与え方を紹介しました。
今回紹介した技術のうちのいくつかは、少々ハック的な技術でしたが、このような技術はほかにも様々あるかと思われますので、調べてみると面白いかもしれませんね。