Deltaライブラリを使って、プレイヤーを自由に吹き飛ばそう！

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

## はじめに
この記事は、ある程度データパックなどが使える人向けの記事です。
functionを作って実行したり、スコアボードが扱える人を基準としています。

<font color="red">プレイヤーのNBTはコマンドでは変更できません。</font>コレのせいで色々悩まされるのが俺たちコマンド勢と言っても過言じゃないです。**過言じゃないよね？**

NBTで管理されてるものの１つで、なおかつコマンドで物を作る上でいじりたくなるのがMotionだと思います。猛スピードで突進する技が作りたいとか、敵のカスタム攻撃にノックバックを付けたいとか…

やろうと思えばAreaEffectCloudとか、ぎっちり詰めたスライムの押し出し判定とかで作れないこともないんですが、今日はそれ以外の方法を紹介します。**Deltaライブラリ**を使いましょう。

## Deltaライブラリって何？
**BigPapi13**氏が作ったデータパックで、大雑把に言えばクリーパーの爆風を使って、指定の威力でプレイヤーをふっとばす事ができます。爆風を使っているといっても、地形が壊れたり、周囲の他のモブを一緒に飛ばしたりはしないので安心して使えます。また、1.20.2以降なら爆発音が聞こえたり、爆風が見えたりする心配がないのも嬉しいポイントです。

## 導入方法
https://github.com/BigPapi13/Delta/tree/no-rp

このリポジトリにアクセスし、zipでダウンロードします。
今回はリソースパックが必要ないバージョンを使います。

緑のボタンを押して"**Download ZIP**"を選択。リリース欄がないリポジトリからDLするときはこの方法になるから覚えておこう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3051829/bfaeb52c-b1ec-5f32-7b01-862f6f562828.png)

ダウンロードしたファイルを解凍し、中に入ってる**Delta-no-rp**を開くと中がこんな感じになってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3051829/94eab7dd-e1b5-f9ae-9288-9eb1f406b4d8.png)
この中の**delta_datapack**がデータパックになっているので、これをいつもデータパック入れるのに使ってる場所である**datapacks**フォルダに入れればそれで使えるようになります。

### データパック配布する人向け
自分で使う分とか、ワールドまるごと配布するのならデータパックとして導入すればいいのですが、データパックとして配布するときとかはそうもいかないので、自分のデータパックに組み込む必要があります。

中に入ってた**Delta-no-rp**の中にある**delta**を、**(データパック名)/data**の中に配置します。
私のデータパックを例にすると、こんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3051829/fe77bef2-6a75-e118-c928-2520394a9273.png)

**Delta-no-rp**の中に入っている**minecraft**に関しては、自分のデータパックに**load.json**が既に存在しなければ、そのまま配置して問題ありません。**(データパック名)\data\minecraft\tags\functions**を確認してみてください。

自分のデータパックに既に**load.json**が存在しているのなら、そのファイルに、deltaのロード時の処理を追加します。記述はこんな感じです。
```
{
  "values": [
    "example:load",
    "delta:internal/technical/load"
    ]
}
```

## 実際に使ってみよう
導入には成功したと思うので、次は実際に動かしてみましょう。

### 特定の方向にプレイヤーを吹き飛ばす

新しいファンクションを作って、中にこう書いてみよう。
```
scoreboard players set $strength delta.api.launch 10000
function delta:api/launch_looking
```
そしてそのファンクションをプレイヤーから実行してみると、今向いている方向に向かって移動したと思います。上の方を向いて実行してみると、ジャンプも可能です。

詳しい仕様を見ていきましょう。"**10000**"の部分は移動させる強さです。強さが10000の場合、プレイヤーは1ブロック/tickで押し出されます。

**function delta:api/launch_looking**を実行したタイミングで、指定した強さで打ち出されます。ここの部分に、execute rotatedなどを使って角度を指定してやることで、向いている方向以外にも飛ばすことが可能です。

これらを踏まえたうえで、ちょっとコマンドをいじってみました。
```
scoreboard players set $strength delta.api.launch 30000
execute rotated ~ -25 run function delta:api/launch_looking
```
これを実行すると、向いている方向に大ジャンプを行ったと思います。
コマンド的には、強さ30000で、縦-25度の角度で打ち出すといった感じです。

### XYZそれぞれのベクトルを指定して飛ばす
```
scoreboard players set $x delta.api.launch 500
scoreboard players set $y delta.api.launch 12000
scoreboard players set $z delta.api.launch -3125
function delta:api/launch_xyz
```

こちらはXYZそれぞれのベクトルを個別に指定するタイプです。
強さの数値に関してなどは先程と同じ。
こちらは、乗ると所定の場所まで飛ぶジャンプ台など、そういったものに使うのに向いていそうです。

### クリーパーを使って直接飛ばす
```
function delta:internal/subtick/begin_launch_context
summon creeper <x y+1000 z> {Tags:["delta.launcher","delta.init"],Silent:1b,Invulnerable:1b,ExplosionRadius:-1b,Fuse:0s,PersistenceRequired:1b}
scoreboard players operation $temp delta.internal.id = @s delta.internal.id
execute as @e[type=creeper,tag=delta.init] at @s run function delta:internal/summon/initialize_creepers
function delta:internal/subtick/end_launch_context
```
細かい制御が必要な場合は直接クリーパーを召喚することもできる…とのことですが、正直使い方が分かっていません。基本的には使うことがないと思います。

### 共通の仕様
これらの処理は、現在のMotionに追加する形で実行されます。
これが何を意味するのかというと、例えば、高速落下しているときに上向きのパワーで飛ばそうとしても、落下速度のほうが勝って全く動かないことがあったりします。
これが気になるようなら、Deltaで移動させる前にMotionをリセットすることで解決できます。
絶対座標にTP→すぐ現在の座標にTP、などの処理で簡単にリセット可能です。
```
tp @s 0 0 0
tp @s ~ ~ ~
scoreboard players set $strength delta.api.launch 10000
function delta:api/launch_looking
```
### 使う上での注意

#### 防具の爆発耐性に影響される
動かすのに爆発を使っている都合で、<font color="red">防具に強力な爆発耐性エンチャントを付与していると全然飛ばなくなります</font>。爆発耐性エンチャントに爆風からのノックバックを減らす効果も付いているのが原因です。防具を一瞬だけ外して、処理が終わってから防具をまた戻すという方法で回避可能で、一応プルリクエストを送ってはいるのですが、これが通らないぶんにはどうしようもないのです。

#### MSPTの不一致
MSPT（**M**illi **S**ecnds **P**er **T**ick)、1ティック分の処理の所要時間のことのようです。
サーバーとクライアントの更新速度は変動する可能性があり、TickごとにDeltaを呼び出すと、この変動が大きいときには威力の不整合が発生するそうです。一定の力で移動させたい場合は、なにかにRideさせてそれを動かす、浮遊エフェクトを使う、といった方法を使うほうが確実、とのことです。

#### Tick毎に処理を呼び出した際の同期ズレ
接続遅延が1tickを超えるサーバーにおいて、プレイヤーに対して相対TPを繰り返すと、サーバーからはそのプレイヤーが移動していないものとして扱われるというバグがあります。詳しくはこちら: [MC-197855](https://bugs.mojang.com/browse/MC-197855)

Deltaにはテレポートを実行する処理が含まれているので、Tick毎に呼び出すとこれに引っかかってしまうことがあり、<font color="red">クライアントの画面側からは動いているように見えるのに、他のプレイヤーから見ると全く動いていない</font>、という現象が発生してしまいます。

一応、1Tickおきに実行するなどの方法で回避は可能ですが、今度は威力の調整がしづらくなるという問題が発生しますし、何よりコマンドが長くなって面倒です。
```
# 数を定義しておく
    scoreboard players set $2 Const 2
    
# Tick加算
    scoreboard players add @s Tick 1

# 実行時間を移す
    scoreboard players operation $Interval Temporary = @s Tick

# 1Tickおきに実行
    scoreboard players operation $Interval Temporary %= $2 Const
    scoreboard players set $strength delta.api.launch 10000
    execute if score $Interval Chuz.Temporary matches 0 run function delta:api/launch_looking

# リセット
    scoreboard players reset $Interval Temporary
```

#### モブのキル数が増える
Deltaの打ち出し処理を一回実行する度にコウモリが二匹死にます。統計画面などを見るときっちり二匹倒したことになっています。コウモリのキル数をカウントするスコアボードが作動するといったことも考えられます。そんなスコアボード使うことないと思いますが。

これも対策してみたのですが、今度はモブのデスポーン処理に頼ることになるから若干不安が残るのと、これまたプルリクエストが通らないとどうしようもないです。

## 今日のまとめ
Deltaを使えばプレイヤーを動かせちゃうぞ！夢のMotion変更だ！
導入さえ済ませちゃえば実行するコマンド自体は簡単！
ただし実行する上でちょっと気にしないといけない点があるのは注意だ！

## 最後に
私はモブの押し出し判定や、浮遊エフェクトなどを使ってMotion変更を無理やり実装したことがありますが、それなんかよりよっぽどスマートな処理でプレイヤーを動かせるのは嬉しいものです。
ただ、実際にプレイヤーを動かせるとなった途端、何をすればいいのか分からなくなってしまったものです。