GUIメニュープラグインを作る(Spigot/Java)

### 開発環境
・Windows11
・IntelliJ IDEA
・Spigot API ver:1.18.2
## GUIメニューとは
![2022-06-17_19.46.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2403016/d58790b2-c005-4bb7-fd51-005112fa3eb7.png)
こんなかんじ(↑)の直感的に操作できるメニューのことです。
大手から中小鯖まで様々なサーバーで独自のサーバーメニューが開発、運用されています。

## GUIメニューを作ってみよう
と言うわけで早速作ってみましょう！
面倒だなあと思ったらSkriptなどを導入することをお勧めします。
ここから先は自己満ですから！

それと今回は木の棒をクリックしたらメニューが開くタイプにします

### ステップ1
PlayerInteractEvent、InventoryClickEvent、InventoryCloseEventのクラスを作ってListenerで実装しましょう。
PlayerInteractEventのコードを載せておきます。

:::note info
同様にInventoryClickEvent、InventoryCloseEventも実装しておいてください。
:::
```java:PlayerinteractEventTest.java
実装

public class PlayerInteractEventTest implements Listener{
    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event){
    //ここに処理を書きます
    }
```

### ステップ2
`event.getAction()`や`event.getItem()`,`event.getHand()`を正規表現や`indexOf`などと組み合わせて使い、木の棒を右クリックした場合にのみ条件を満たすように書きます。

### ステップ3
プレイヤーに見せる部分を書いていきましょう。
まずはOwnerがnull、スロット数9、名前がMenuのインベントリを作成します。

:::note warn
スロット数は9の倍数かつ56以下になるように書いてください。
条件に合致しないスロット数を入力するとエラーを吐きます。
:::
```java:PlayerInteractEventTest.java
Inventory inv = Bukkit.createInventory(null,9,"Menu");
```
それから各スロットにアイテムをセットしていきます。

個人的に別ファイルにClassを作成してそこから呼び出すようにした方が分かりやすくてよいのではないかと思います。
その例を記載しておきます。
(まだクラスなどについてあまり理解していないのでごちゃごちゃしているのはご容赦ください。)
```java:ViewItems.java
public class ViewItems{
  public ItemStack ViewItemsMain(){
    ItemStack zero = new ItemStack(Material.DIAMOND_SWORD,1);
    ItemMeta zeroMeta = zero.getItemMeta();
    zeroMeta.setLore(Arrays.asList("First line","Second line"));
    zeroMeta.setDisplayName("Example Sword");
    zero.setItemMeta(zeroMeta);
    return zero;
  }
}
```
```java:PlayerInteractEventTest.java
inv.setItem(0,new ViewItems().ViewItemsMain());
```

### ステップ4
ステップ3までで、invに表示するアイテムの情報を登録することができたのでいよいよプレイヤーに表示します。
```java:PlayerInteractEventTest.java
Player sender = event.getPlayer();
sender.openInventory(inv);

sender.addScoreboardTag("MenuOpen");
```
表示した後に音を鳴らしたりするときのためにPlayer型のsenderを作成しました。
`sender.openInventory(inv);`で色々修飾したinvをsenderに表示します。
これでsenderにGUIメニューを表示することができました。

最後の行はsenderにMenuOpenというTagを付与しています。
用途については以下のステップで解説します。

## コーヒーブレイク
ここまでのステップでGUIメニューを実装することができましたが、実はまだ核となる機能を実装することができていません。
その機能とはずばり「GUIメニューのアイテムを取り出せないようにする」です。

メニューのアイテムが取り出せないのは当たり前のことですが、ここまで書いてきたコードではそのような機能を実装することはできません。
ちなみにここからはJava初心者の私がクラス色々悩んだ挙句、苦肉の策としてひねり出したアイデアを実装していくので、
「もうここまで書いてくれれば大丈夫！」という方とはここでお別れとなります。お疲れさまでした。

***

`"spigot how to create gui inventory"`などとGoogleで調べるとSpigot公式のチュートリアル記事などが出てきますが、そこらにはイベントのリスナが1つのクラスに2つ実装されています。
私はそこがいまいち理解できなかった(掲示板などを覗いても同時に2つは機能しないと書かれている)のでTagを使うことにしました。

というわけで、ステップ4の最後に書いた`sender.addScoreboardTag("MenuOpen");`を使い、
「表示したGUIメニューからアイテムを取り出せないようにする」機能を実装していきます。


## ステップ5
ステップ1で実装したまま触れてこなかったInventoryClickEventとInventoryCloseEventについて解説していきます。

InventoryClickEventはキャンセル可能で、インベントリのスロットをクリックすると発生するイベントです。
InventoryCloseEventは同様にキャンセル可能でインベントリを閉じると発生するイベントです。


___

どのような流れになるのか簡単に説明しておきます。↓
- InventoryClickEventが呼び出された時、対象となるプレイヤーが`OpenMenu`タグを持つかチェックして、持っていたらイベントをキャンセルしてアイテムを取り出せないようにする。

- InventoryCloseEventが呼び出された時に付与されているOpenMenuタグを削除する。


それでは詳しく見ていきましょう。

```java:InventoryClick.java
public class InventoryClick implements Listener {
    @EventHandler
    public void onInventoryClick(InventoryClickEvent event){
        Set<String> UserTags = event.getWhoClicked().getScoreboardTags();
        if(UserTags.contains("MenuOpen")){
            event.setCancelled(true);
        }
    }
}

```
`event.getWhoClicked().getScoreboardTags();`で対象のプレイヤーが持っているタグすべてをSetインターフェースで取得することができます。
そのSetを`.contains("MenuOpen)"`でMenuOpenタグが含まれていないかチェックし、含まれている(True)だった場合`event.setCancelled(true);`でイベントがキャンセルされます。

:::note info
setCancelled(true);でキャンセルです。
ここをfalseにしてしまうとキャンセルしないになってしまうので気を付けましょう。
:::

## ステップ6
いよいよラストです。
といってもステップ5でGUIメニューからのアイテム取り出し対策について9割がた書き終わったので残りを説明して終わりにします。

最後にInventoryCloseEventで行う処理ですが、「GUIメニューを閉じる際に付与されていたMenuOpenタグを削除する」だけです。
```java:InventoryClose.java
public class InventoryClose implements Listener {
    @EventHandler
    public void onInventoryClose(InventoryCloseEvent event){
        Set<String> UserTags = event.getPlayer().getScoreboardTags();
        if(UserTags.contains("MenuOpen")){
            event.getPlayer().removeScoreboardTag("MenuOpen");
        }
    }
}
```
InventoryClickの時と同じくSetで持っているタグすべてを取得し`.contains("MenuOpen");`でMenuOpenタグを持つかチェックしています。
持っていた場合は`event.getPlayer().removeScoreboardTag("MenuOpen");`でMenuOpenタグを削除します。
これでGUIメニューを開いた後に自分のインベントリやチェストを開いた際にはきちんとアイテムを動かすことができます。

![k5s0v-j9lgy.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2403016/f35cd36e-915a-8845-3df4-8db8e808d4fd.gif)

上記の動画では必死にGUIメニューのアイテムを持ってこようとしていますが取得することはできていません。

## おわり
これで解説を終わります。初学者なりに工夫して実装してみました。
それでは良いプラグインライフを！

-坂木啞ルカ-