SpigotプラグインでProtocolLibを使ってみる

#ProtocolLibとはなんぞや
https://github.com/dmulloy2/ProtocolLib/

マインクラフトのサーバーとクライアント間で送受信されるパケットの操作を簡単にするプラグイン。

#パケット操作の例
* 偽のモブのスポーン
* 偽のブロックの設置
* 別の種類のMobに変身(応用)

他にも色々できる。

#通常の場合
ProtocolLibを使わない通常の場合、BukkitAPIのみでパケット操作はできないので、NMSを扱わないといけない。
###送信時
```java
Packet packet = new PacketPlayOutBlockChange(new BlockPosition(0, 0, 0), Blocks.STONE.getBlockData());
((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
```
###受信時
```java
    private void injectPlayer(Player player) {
        ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() {

            @Override
            public void channelRead(ChannelHandlerContext channelHandlerContext, Object packet) throws Exception {
                // 受信時の処理
                super.channelRead(channelHandlerContext, packet);
            }

            @Override
            public void write(ChannelHandlerContext channelHandlerContext, Object packet, ChannelPromise channelPromise) throws Exception {
                // 送信時の処理
                super.write(channelHandlerContext, packet, channelPromise);
            }

        };

        ChannelPipeline pipeline = ((CraftPlayer) player).getHandle().playerConnection.networkManager.channel.pipeline();
        pipeline.addBefore("packet_handler", player.getName(), channelDuplexHandler);

    }
```

###PlayerConnectionクラスを継承する方法もある

```java
public class CustomPlayerConnection extends PlayerConnection {

    public CustomPlayerConnection(EntityPlayer entityPlayer) {
        super(MinecraftServer.getServer(), entityPlayer.playerConnection.networkManager, entityPlayer);
    }
    
    @Override
    public void a(PacketPlayInBlockPlace packetplayinblockplace) {
        // ブロック設置のパケットが送られてきたときの処理
        super.a(packetplayinblockplace);
    }

}
```

```java
// 参加時にplayerConnectionを自作クラスに書き換える
@EventHandler
public void onJoin(PlayerJoinEvent playerJoinEvent) {
    EntityPlayer entityPlayer = ((CraftPlayer) playerJoinEvent.getPlayer()).getHandle();
    entityPlayer.playerConnection = new CustomPlayerConnection(entityPlayer);
}
```
###NMSは触りたくない！！！！！
ということでProtoclLibを使ってみよう！
#導入
###plugin.ymlのdependにProtocolを追加
```plugin.yml
name: PluginName
version: 1.0
main: plugin.yourplugin
api-version: 1.16
depend: [ProtocolLib]
```
###依存関係の追加(Maven)
```pom.xml
<repository>
  <id>dmulloy2-repo</id>
  <url>https://repo.dmulloy2.net/repository/public/</url>
</repository>

<dependency>
  <groupId>com.comphenix.protocol</groupId>
  <artifactId>ProtocolLib</artifactId>
  <version>4.6.0</version>
</dependency>
```
###依存関係の追加(Gradle)
```build.gradle
repositories {
    maven { url "https://repo.dmulloy2.net/repository/public/" }
}

dependencies {
   compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.6.0";
}
```

※2021/4/22現在、4.6.0が最新
#パケットを送信する
試しに、ブロックを設置するパケットを送信してみます。

```java
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();

// パケットコンテナを作成
PacketContainer packetContainer = protocolManager.createPacket(PacketType.Play.Server.BLOCK_CHANGE);

// パケットのフィールドへ書き込み
packetContainer.getBlockPositionModifier().write(0, new BlockPosition(x, y, z));
packetContainer.getBlockData().write(0, WrappedBlockData.createData(Material.STONE));

// 送信
try {
    Player player; // 送信したいプレイヤー
    protocolManager.sendServerPacket(player, packetContainer);
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
```
※BlockPositionクラスはNMSのではなく、ProtocolLibのもの

送信すると、サーバーにブロックが置かれず、クライアントのみにブロックが置かれる！ (更新されるとで元に戻る)
#パケットの受信する
パケットの受信とは、サーバーにプレイヤーからの偽のパケットを送信するイメージ。

試しにプレイヤーからチャットが送られてきた、というパケットをサーバーに受信させてみる。

```java
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();

// パケットコンテナを作成
PacketContainer packetContainer = protocolManager.createPacket(PacketType.Play.Client.CHAT);

// パケットのフィールドへ書き込み
packetContainer.getChatComponents().write(0, WrappedChatComponent.fromText("こんにちは"));
packetContainer.getChatTypes().write(0, EnumWrappers.ChatType.CHAT);

// 送信
try {
    Player player; // 送り主を指定
    protocolManager.recieveClientPacket(player, packetContainer);
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
```

受信すると、プレイヤーから送られてもないチャットがサーバーに受け取られる
#パケットの送信、受信時に処理を加える
BukkitAPIのイベントリスナーのパケット版的なことができる
###クラスの定義
```java
public class PacketListener extend PacketAdapter {

	public PacketListener(Plugin plugin, PacketType... types) {
        super(plugin, types);
    }

	@Override
    public void onPacketReceiving(PacketEvent event) {
		// パケットを受け取った時の処理
	}

	@Override
	public void onPacketSending(PacketEvent event) {
		// パケットを送った時の処理
	}
}
```
###リスナー登録
```java
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
protocolManager.addPacketListener(new PacketListener(plugin, types));
```
コンストラクタの第二引数でイベントを呼ぶパケットの種類を指定できる。
#まとめ
気が向いたらもうちょっと詳しい記事を出します。