【Minecraft】Bukkit/Spigotプラグインで時間操作

*2020年5月5日：コード上に些細なミスが有りましたので修正しました。*

---

Bukkitのプラグイン作成の初めの方で、つまずいた点をメモ。

#したいこと
Minecraftのチャット欄に特定のワードを入れると、トリガーがひかれてゲーム内時間を設定する。ここでは例として「朝」と入力すれば時刻0（朝）に変更するようなプラグインを作成する。

#ダメな例
```Java:main.java
//パッケージ文、インポート文は省略
public class main extends JavaPlugin{
    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(new eventListener(this), this);
    }
}
```

```Java:eventListener.java
//パッケージ文、インポート文は省略
public class eventListener implements Listener {
    private final main m;
    public eventListener(main m_){m=m_;}

    @EventHandler
    public void onPlayerChat(AsyncPlayerChatEvent event){
        if(event.getMessage().equals("朝")){
            event.getPlayer().getWorld().setTime(0L);
            event.getPlayer().sendMessage("ワード「朝」を検知しました。");
            m.getLogger().info(event.getPlayer().getName()+"さんがワード「朝」を発言しました");
        }
    }
}
```

プレイヤーが送ったチャットは`AsyncPlayerChatEvent`でキャッチでき、プラグイン側で自由に操作できる。また、[ここに書かれている](https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/World.html)、Worldは、イベントハンドラ内では`event.getPlayer().getWorld()`で取得できる。

面倒なのは、これでコンパイルが通るということ。

#なぜダメなのか
コンパイルが成功し、実際に導入してみてもエラーは出ない。しかし、実際に「朝」と入力したときに、サーバー側で2つのエラーが出る。

```
[00:00:00 ERROR]: Could not pass event AsyncPlayerChatEvent to hogehoge_plugin v1.0.0
org.bukkit.event.EventException: null
	at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:312) ~[craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
	at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
～略～
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:461) [craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) [craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
	at java.lang.Thread.run(Thread.java:830) [?:?]
Caused by: java.lang.IllegalStateException: TimeSkipEvent cannot be triggered asynchronously from another thread.
	at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:510) ~[craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
	at org.bukkit.craftbukkit.v1_15_R1.CraftWorld.setFullTime(CraftWorld.java:803) ~[craftbukkit-1.15.2.jar:git-Bukkit-8160e29]
～略～
```

[このドキュメント](https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/player/AsyncPlayerChatEvent.html)でも書かれている通り、`AsyncPlayerChatEvent`基本的に非同期で実行される。しかし、`event.getPlayer().getWorld().setTime(0L);`は同期的に実行しなければならない。

#解決方法
いくつか解決方法はあるが、一番簡単な匿名クラスの実装でエラーを回避する。

```Java:main.java
//そのままで良い
```

```Java:eventListener.java
//パッケージ文、インポート文は省略
public class eventListener implements Listener {
    private final main m;
    public eventListener(main m_){m=m_;}

    @EventHandler
    public void onPlayerChat(AsyncPlayerChatEvent event){
        if(event.getMessage().equals("朝")){
            m.getServer().getScheduler().runTask(m, new Runnable() {
                @Override
                public void run() {event.getPlayer().getWorld().setTime(0L);}
            });
            event.getPlayer().sendMessage("ワード「朝」を検知しました。");
            m.getLogger().info(event.getPlayer().getName()+"さんがワード「朝」を発言しました");
        }
    }
}
```

非同期な`AsyncPlayerChatEvent`から同期的な処理を呼び出すために、`org.bukkit.scheduler`のスケジューラーを使用している。`runTask`は、登録されたクラスを次のティック（サーバーティック）に実行する。実行まで少し待ちたい場合は、かわりに`runTaskLater`を使う。



<br>
<br>


以上。