カスタムエンチャントで銃を作ろう


# はじめに
カスタムエンチャントは実験的機能です。
基本的な内容は、過去の[自分の記事](https://qiita.com/Hirobao1/items/c6c307cdbad0589d43a3)に書いています。

https://qiita.com/Hirobao1/items/c6c307cdbad0589d43a3

カスタムエンチャントでできることはとても多く、全て紹介するのは大変なので、今回は銃の製作を例として紹介していきます。
エンチャントが全く分からない人も、「こんなことができるんだ、へぇ～」くらいに聞き流してもらえればなと思います。
※これは銃を作る事が目的ではなくエンチャントを使う事を目的にしています。

実際に記述する際は、misode氏の[エンチャントジェネレーター](https://misode.github.io/enchantment/)が使いやすくおすすめです。

:::note warn
Java版 Minecraft 1.21.3 時点の情報です
本記事ではバグの挙動を利用している場合があります
:::

# カスタムエンチャントとは
エンチャントを自由にカスタマイズできる1.21から追加されたデータパックの新要素です。
バニラエンチャントの拡張要素だけでなく、バニラには無いエンチャントやコマンドと合わせた使い方ができ、全く新しいエンチャントを作ることができます。

# エンチャント基本知識
氷渡りのついた防具を装備しているゾンビは水を凍らせたり、パンチのついた弓から放った矢は当てるとノックバックが増加するように、エンチャントはプレイヤー以外のリビングエンティティや発射された矢にも付与できます。
矢のエンティティデータには`wepon`タグがあり、何のエンチャントされた武器から放たれたかという情報が保持されています。
リビングエンティティにはゾンビやスケルトンのようなMOBだけでなく、アーマースタンドも含まれます。

# 弾の制作
コマンドで銃弾を正確に再現するとき、基本的に再帰関数を使用すると思いますが、処理が多くなったり角抜けしたりと正確な判定を取るのが難しくもあります。
今回はエンチャントを使用することもあり弾には矢を使用します。
矢を使うことで、角抜けや当たり判定が正確になり、誰が攻撃したかなどが分かりやすくなります。

## 矢の見た目
矢を使うにあたって問題になるのが、矢が見えることです。
銃を撃っていて矢が出てくるのはおかしいので消して使います。
リソースパックで矢のテクスチャを消すことで解決できるのですが、普通に使う矢は見えるようにしたいので少し工夫します。
リソースパックには`arrow`(矢)と`tipped_arrow`(効果付きの矢)の二種類あり、`tipped_arrow`の方のテクスチャを消します。
デフォルトでは効果付きの矢も`arrow`のテクスチャを使用しているので影響はありません。
しかし、コンポーネントの`potion_contents`の`custom_color`を指定した時のみ`tipped_arrow`のテクスチャを参照します。
矢がヒットした音やブロックに刺さった音を出したくない場合は、`Silent:true`も追加します。
```coffee:tipped_arrowが参照される矢の召喚コマンド
summon arrow ~ ~ ~ {item:{id:"arrow",components:{potion_contents:{custom_color:1}}}}
```
注意として、ブロックに刺さってから30秒で通常の矢に戻ります。
今回は着弾したときにキルするため問題ないです。
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">特定の矢だけ見えなくする <a href="https://t.co/5xwpXWWBkV">pic.twitter.com/5xwpXWWBkV</a></p>&mdash; ひろばお🙃コマンド (@Hirobao1) <a href="https://twitter.com/Hirobao1/status/1872055241140846603?ref_src=twsrc%5Etfw">December 25, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 刺さらない矢
矢はプレイヤーに撃つと、体に刺さった矢が表示されてしまいます。
これはカスタムエンチャントで解決できます。
使用するエンチャント効果は`projectile_piercing`と`post_attack`です。
`projectile_piercing`は貫通のエンチャントに使われているものです。
発射物に貫通する効果を持たせることができます。(召喚された矢の`PierceLevel`が上がる)
貫通する矢はプレイヤーに刺さることはありません。
コマンドで矢を召喚する場合は、エンチャントではなく矢に`PierceLevel`タグをつけます。

`post_attack`は火属性や棘の鎧のエンチャントに使われているものです。
攻撃したことや、攻撃されたことをトリガーにいろいろな効果を付けることができます。
今回は矢がエンティティを攻撃した時コマンドを実行し、自身をキルするようにします。

ついでに、ブロックに刺さった時もキルするようにします。
これには`hit_block`のエンチャント効果を使います。
`hit_block`は召雷のエンチャントに使われているものです。
ブロックに発射体が刺さった時や、ブロックを左クリックしたことをトリガーにいろいろな効果を付けることができます。

<details><summary><font color=#1a73e8> 矢がエンティティとブロックに当たった時にキルするエンチャント</font></summary> 

```json:arrow_kill.json
{
  "description": "arrow_kill",
  "supported_items": [],
  "weight": 1,
  "max_level": 1,
  "min_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "max_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "anvil_cost": 0,
  "slots": [
    "hand"
  ],
  "effects": {
    "post_attack": [
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "direct_attacker",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        },
        "enchanted": "attacker",
        "affected": "damaging_entity"
      }
    ],
    "hit_block": [
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "this",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        }
      }
    ]
  }
}
```
エンチャントから実行するコマンド
```:kill.mcfunction
kill @s
```
</details>

```coffee:矢を召喚するコマンド
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_kill:1}}},Owner:$(UUID)}
```

実行時にマクロを使用し、矢のOwnerタグにプレイヤーのUUIDとMotionを指定します。

## Motionの指定
Motionの取得方法は、マーカーエンティティを座標(0,0,0)の位置から掛けたいMotionの値だけ実行者の視線方向に移動させ、その位置がそのままMotionの値になります。
```coffee:実行するコマンド
execute at @s summon marker run function motion
```
```coffee:motion.mcfunction
# Motion計算
execute positioned 0.0 0.0 0.0 run tp @s ^ ^ ^3
data modify storage adcl:motion Pos set from entity @s Pos
execute store result storage adcl:motion x double 0.01 run data get storage adcl:motion Pos[0] 100
execute store result storage adcl:motion y double 0.01 run data get storage adcl:motion Pos[1] 100
execute store result storage adcl:motion z double 0.01 run data get storage adcl:motion Pos[2] 100
# Marker消去
kill @s
```

## 弾のダメージ
次に弾のダメージを決めていきます。
矢のダメージ量は速度に比例します。
また、クリティカルの場合ランダムでダメージが加算されます。
矢はエンチャントなどでダメージを0にした場合、プレイヤーに刺さらず跳ね返ります。
そのため最低でも1のダメージは与える必要があります。

ここで使用するエンチャント効果は`damage`です。
`damage`はダメージ増加や特攻系エンチャントに使われているものです。
エンティティに与えるダメージの値を変更できます。 
矢のダメージ量の計算式はダメージ×速度(block/tick)で小数点以下は切り上げです。
矢の速度がある程度早くなっても1を超えないような小さな値にします。

```json:damageを1にするエンチャントのeffects
{
  "damage": [
    {
      "effect": {
        "type": "set",
        "value": 1e-8
      }
    }
  ]
}
```

クリティカルがあるとそこからダメージにランダムな値が加算されるので、召喚時には矢の`crit`タグを`false`にしておきます。
ダメージ量を増加させたい場合は、`post_attack`から`damage_entity`などを使用して追加の効果をつけます。
`post_attack`はダメージを与えた時に追加で効果を付けるものです。
なので、`damage`エンチャントによって1ダメージ与えた後に処理されます。
`damage_entity`はエンティティにダメージを与える効果です。
ダメージ量は操作したいのでエンチャントレベルによって変化するようにします。

<details><summary><font color=#1a73e8> レベルによってdamageが変化するエンチャント</font></summary> 

```json:エンチャントのeffects
{
  "post_attack": [
    {
      "requirements": {
        "condition": "entity_properties",
        "entity": "direct_attacker",
        "predicate": {
          "type": "arrow"
        }
      },
      "effect": {
        "type": "damage_entity",
        "damage_type": "arrow",
        "min_damage": {
          "type": "linear",
          "base": 0,
          "per_level_above_first": 1
        },
        "max_damage": {
          "type": "linear",
          "base": 0,
          "per_level_above_first": 1
        }
      },
      "enchanted": "attacker",
      "affected": "victim"
    }
  ]
}
```
</details>

エンチャントレベルが1の時は追加で0ダメージを与え、レベルが上がるごとに与えるダメージが1ずつ増えていきます。
`damage`エンチャントで最初に1ダメージを与えているので、この矢はエンチャントレベルと同じ値のダメージ量になります。

## パーティクルの表示
矢のテクスチャを消したため、銃を撃ったとしても何が起こっているのか分からない状態です。
そのため、弾道や弾が敵や壁に当たった時、パーティクルを表示して視覚的に分かるようにします。
使用するエンチャント効果は、壁や敵に当たった判定は`post_attack`と`hit_block`を、弾道は`tick`を使用します。

<details><summary><font color=#1a73e8> 壁や敵に当たった時にパーティクル表示</font></summary> 

```json:エンチャントのeffects
{
  "post_attack": [
    {
      "requirements": {
        "condition": "entity_properties",
        "entity": "direct_attacker",
        "predicate": {
          "type": "arrow"
        }
      },
      "effect": {
        "type": "spawn_particles",
        "particle": {
          "type": "damage_indicator"
        },
        "horizontal_position": {
          "type": "in_bounding_box",
          "scale": 1
        },
        "vertical_position": {
          "type": "in_bounding_box",
          "offset": -0.25,
          "scale": 0.5
        },
        "horizontal_velocity": {},
        "vertical_velocity": {}
      },
      "enchanted": "attacker",
      "affected": "victim"
    }
  ],
  "hit_block": [
    {
      "requirements": {
        "condition": "entity_properties",
        "entity": "this",
        "predicate": {
          "type": "arrow"
        }
      },
      "effect": {
        "type": "spawn_particles",
        "particle": {
          "type": "smoke"
        },
        "horizontal_position": {
          "type": "entity_position"
        },
        "vertical_position": {
          "type": "entity_position"
        },
        "horizontal_velocity": {},
        "vertical_velocity": {}
      }
    }
  ]
}
```

</details>

`tick`はいろいろな効果を毎tick実行できます。
今回は毎tickパーティクルを表示するために使いますが、`requirements`でいろんな条件による弾の制御にも利用でき、撃った後に矢の軌道を変えたり、ダメージを変更したりなどにも使えたりします。
`tick`エンチャントは矢単体ではできないのでアーマースタンドを使用します。
矢の召喚時にエンチャントを持ったアマスタを矢に乗せて召喚します。

<details><summary><font color=#1a73e8> 弾道パーティクル表示エンチャント</font></summary> 

```json:arrow_particle.json
{
  "description": "弾道表示",
  "supported_items": [],
  "weight": 1,
  "max_level": 1,
  "min_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "max_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "anvil_cost": 0,
  "slots": [
    "armor"
  ],
  "effects": {
    "tick": [
      {
        "effect": {
          "type": "spawn_particles",
          "particle": {
            "type": "crit"
          },
          "horizontal_position": {
            "type": "entity_position"
          },
          "vertical_position": {
            "type": "entity_position",
            "offset": -0.5
          },
          "horizontal_velocity": {},
          "vertical_velocity": {}
        }
      },
      {
        "requirements": {
          "condition": "inverted",
          "term": {
            "condition": "entity_properties",
            "entity": "this",
            "predicate": {
              "type": "armor_stand",
              "vehicle": {
                "type": "arrow"
              }
            }
          }
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        }
      }
    ]
  }
}
```

</details>

バグでアマスタだけ残らないように矢に乗っていないアマスタに対してkillする処理も入れています。

次は矢の方に掛かるエンチャントです。
エンティティに当たった時には`post_attack`で`damage_indicator`パーティクルを、壁に当たった時は`hit_block`で`smoke`パーティクルを表示します。
<details><summary><font color=#1a73e8> 矢がヒット時のパーティクル表示エンチャント</font></summary> 

```json:エンチャントのeffects
{
  "post_attack": [
    {
      "requirements": {
        "condition": "entity_properties",
        "entity": "direct_attacker",
        "predicate": {
          "type": "arrow"
        }
      },
      "effect": {
        "type": "spawn_particles",
        "particle": {
          "type": "damage_indicator"
        },
        "horizontal_position": {
          "type": "in_bounding_box",
          "scale": 1
        },
        "vertical_position": {
          "type": "in_bounding_box",
          "offset": -0.25,
          "scale": 0.5
        },
        "horizontal_velocity": {},
        "vertical_velocity": {}
      },
      "enchanted": "attacker",
      "affected": "victim"
    }
  ],
  "hit_block": [
    {
      "requirements": {
        "condition": "entity_properties",
        "entity": "this",
        "predicate": {
          "type": "arrow"
        }
      },
      "effect": {
        "type": "spawn_particles",
        "particle": {
          "type": "smoke"
        },
        "horizontal_position": {
          "type": "entity_position"
        },
        "vertical_position": {
          "type": "entity_position"
        },
        "horizontal_velocity": {},
        "vertical_velocity": {}
      }
    }
  ]
}
```
</details>

**エンチャントから実行するコマンド**
```coffee:kill.mcfunction
execute on passengers run kill @s
kill @s
```
矢からアマスタをキルするために1行目を追加しました。


**矢と防具立てを召喚するコマンド**
```coffee:summon.mcfunction
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,Silent:1b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_gun:1}}},Motion:[$(x)d,$(y)d,$(z)d],Owner:$(UUID),Passengers:[{id:armor_stand,Silent:1b,Marker:1b,Invisible:1b,ArmorItems:[{id:"stone",count:1,components:{enchantments:{"arrow_particle":1}}},{},{},{}]}]}
```

`item`コマンドなどでは、MOBの頭以外の部分には防具しか装備させられませんが、召喚する時には防具以外のアイテムも入れられ、見えないアイテムかつエンチャントを付与できます。（`item_model`コンポーネントの`"air"`でも可）
アマスタとは別に弾の見た目用に、アイテムディスプレイ等を乗せてもいいのですが、矢の速度が速すぎると表示位置がずれるバグがあるので注意してください。

# ファイルのまとめ
いままで1つずつ説明してきましたが、エンチャントの数が多いので全てを1つのエンチャントにまとめます。
`requirements`で矢やアマスタ等の条件を付けることで、複数のエンチャントを1つにまとめることができます。

<details><summary><font color=#1a73e8> 全てをまとめたエンチャント</font></summary> 

```json:arrow_gun.json
{
  "description": "銃のエンチャント",
  "supported_items": [],
  "weight": 1,
  "max_level": 255,
  "min_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "max_cost": {
    "base": 0,
    "per_level_above_first": 0
  },
  "anvil_cost": 0,
  "slots": [
    "any"
  ],
  "effects": {
    "post_attack": [
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "direct_attacker",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "damage_entity",
          "damage_type": "arrow",
          "min_damage": {
            "type": "linear",
            "base": 0,
            "per_level_above_first": 1
          },
          "max_damage": {
            "type": "linear",
            "base": 0,
            "per_level_above_first": 1
          }
        },
        "enchanted": "attacker",
        "affected": "victim"
      },
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "direct_attacker",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "spawn_particles",
          "particle": {
            "type": "damage_indicator"
          },
          "horizontal_position": {
            "type": "in_bounding_box",
            "scale": 1
          },
          "vertical_position": {
            "type": "in_bounding_box",
            "offset": -0.25,
            "scale": 0.5
          },
          "horizontal_velocity": {},
          "vertical_velocity": {}
        },
        "enchanted": "attacker",
        "affected": "victim"
      },
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "direct_attacker",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        },
        "enchanted": "attacker",
        "affected": "damaging_entity"
      }
    ],
    "hit_block": [
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "this",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        }
      },
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "this",
          "predicate": {
            "type": "arrow"
          }
        },
        "effect": {
          "type": "spawn_particles",
          "particle": {
            "type": "smoke"
          },
          "horizontal_position": {
            "type": "entity_position"
          },
          "vertical_position": {
            "type": "entity_position"
          },
          "horizontal_velocity": {},
          "vertical_velocity": {}
        }
      }
    ],
    "damage": [
      {
        "effect": {
          "type": "set",
          "value": 1e-8
        }
      }
    ],
    "tick": [
      {
        "requirements": {
          "condition": "entity_properties",
          "entity": "this",
          "predicate": {
            "type": "armor_stand"
          }
        },
        "effect": {
          "type": "spawn_particles",
          "particle": {
            "type": "crit"
          },
          "horizontal_position": {
            "type": "entity_position"
          },
          "vertical_position": {
            "type": "entity_position",
            "offset": -0.5
          },
          "horizontal_velocity": {},
          "vertical_velocity": {}
        }
      },
      {
        "requirements": {
          "condition": "all_of",
          "terms": [
            {
              "condition": "entity_properties",
              "entity": "this",
              "predicate": {
                "type": "armor_stand"
              }
            },
            {
              "condition": "inverted",
              "term": {
                "condition": "entity_properties",
                "entity": "this",
                "predicate": {
                  "vehicle": {
                    "type": "arrow"
                  }
                }
              }
            }
          ]
        },
        "effect": {
          "type": "run_function",
          "function": "kill"
        }
      }
    ]
  }
}
```

</details>

今回作成したfunctionも下記にまとめて書いておきます。

<details><summary><font color=#1a73e8> 全functionファイル</font></summary> 

```coffee:kill.mcfunction
execute on passengers run kill @s
kill @s
```
```coffee:motion.mcfunction
# Motion計算
execute positioned 0.0 0.0 0.0 run tp @s ^ ^ ^3
data modify storage adcl:motion Pos set from entity @s Pos
execute store result storage adcl:motion x double 0.01 run data get storage adcl:motion Pos[0] 100
execute store result storage adcl:motion y double 0.01 run data get storage adcl:motion Pos[1] 100
execute store result storage adcl:motion z double 0.01 run data get storage adcl:motion Pos[2] 100
# Marker消去
kill @s
```
```coffee:summon.mcfunction
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,Silent:1b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_gun:1}}},Motion:[$(x)d,$(y)d,$(z)d],Owner:$(UUID),Passengers:[{id:armor_stand,Silent:1b,Marker:1b,Invisible:1b,ArmorItems:[{id:"stone",count:1,components:{enchantments:{"arrow_particle":1}}},{},{},{}]}]}
```
</details>


上記のエンチャントとコマンドを入れた後、次のfunctionをプレイヤーを実行者として実行することで弾が発射されます。
```coffee:shoot.mcfunction
playsound entity.firework_rocket.blast master @a ~ ~ ~ 0.5 0.5 0
data modify storage adcl:motion UUID set from entity @s UUID
execute at @s summon marker run function motion
function summon with storage adcl:motion
```

# さいごに
今回は銃を作るという例でカスタムエンチャントを紹介しましたが、まだまだできることは多く想像次第でいろいろな使い方ができます。
自分は以前にカスタムエンチャントの`explotion`（爆発する効果）を使用してプレイヤーに任意の値のMotionを付与するデータパックをgithubに公開しました。

https://github.com/Hirobao1/CustomEnchant_PlayerMotion

カスタムエンチャントが広まりより多くの人たちが使い、開拓されていけばいいなと思います。