KotlinでBukkitプラグインを書く

2024/12/15に公開

この記事は 木更津高専 Advent Calendar 2024 参加記事です。

前 → 920MHzに比べると国技館のBluetoothはカスや by @kokastar
次 → 今年もプロ研が学園祭Webサイト制作をした話 by @naotiki

はじめに

Kotlinの公式ドキュメントにあるGet started with Kotlinでは、Kotlinの特徴について以下のように書いてあります。

Kotlin is a modern but already mature programming language designed to make developers happier. It's concise, safe, interoperable with Java and other languages, and provides many ways to reuse code between multiple platforms for productive programming.

Kotlinの動作するプラットフォームの1つにJava仮想マシン(JVM)があるので、Javaの代わりにKotlinを使用することができます。
さらに、Javaのコードで定義されたメソッドをKotlinで呼び出したり、逆にKotlinのコードで定義されたメソッドをJavaで呼び出すこともできます。

そこで、この記事では、通常Javaで記述されるBukkitプラグインを、Kotlinで書くにあたっての特徴や留意点をまとめたいと思います。

注意点

今回の記事では、Kotlinでプラグインを書く際のポイントについてまとめるため、プラグインの作り方については触れません。
プラグインの作成については、以下のような記事が参考になります。必要ならば併せてお読み下さい。

https://densyakun.hateblo.jp/entry/2020/04/01/153407

https://mcmodding.jp/modding/index.php/Plugin_Tutorial

環境

  • IntelliJ IDEA 2024.3
  • Kotlin 2.1.0
  • Minecraft Development 2024.3-1.8.2

プロジェクトの作成

Minecraft Developmentの導入

ランチャーのPluginsを開き、Minecraft Developmentをインストールします。
Marketplaceでminecraftと検索すれば、大抵最も上位に表示されます。

新規プロジェクトの作成

ランチャーのProjectsの中にあるNew Projectを開き、GeneratorsからMinecraftを選択します。

Plugin NameMain ClassArtifact IDGroup IDはJavaで作成するときと同様に入力します。
Languageは、今回使用するKotlinを選びます。

プログラミング

以下のページに書いてある内容が全てですが、特にプラグインを作成するときに登場するものを紹介します。

https://kotlinlang.org/docs/java-interop.html
https://kotlinlang.org/docs/java-to-kotlin-interop.html

ゲッターとセッター

Java上で定義されたゲッターとセッターは、Kotlin上ではプロパティとして表現されます(synthetic properties)。
したがって、以下のJavaとKotlinのコードは同様に動作します。

Java
package org.example.samplePlugin;

import org.bukkit.plugin.java.JavaPlugin;

public final class SamplePlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Plugin startup logic
        saveDefaultConfig();

        final FileConfiguration config = getConfig();
        final String hoge = config.getString("fuga.piyo");

        getLogger().info("Enabled samplePlugin");
    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        getLogger().info("Disabled samplePlugin");
    }
}
Kotlin
package org.example.samplePlugin

import org.bukkit.plugin.java.JavaPlugin

class SamplePlugin : JavaPlugin() {

    override fun onEnable() {
        // Plugin startup logic
        saveDefaultConfig()

        val hoge = config.getString("fuga.piyo")

        logger.info("Enabled samplePlugin")
    }

    override fun onDisable() {
        // Plugin shutdown logic
        logger.info("Disabled samplePlugin")
    }
}

メソッドの型

Bukkitのオブジェクトを返すメソッドには、必ず@NotNullもしくは@Nullableのアノテーションがあります。
このうち、@NotNullアノテーションがあるメソッドはTを、@NullableアノテーションがあるメソッドはT?を返します。

Kotlin
package org.example.samplePlugin

import org.bukkit.configuration.file.FileConfiguration
import org.bukkit.plugin.java.JavaPlugin
import java.io.InputStream

class SamplePlugin : JavaPlugin() {

    override fun onEnable() {
        // Plugin startup logic

        // @NotNull public FileConfiguration getConfig()
        val hoge: FileConfiguration = config

        // @Nullable public InputStream getResource(@NotNull String filename)
        val fuga: InputStream? = getResource("fuga.json")
    }

    override fun onDisable() {
        // Plugin shutdown logic
    }
}

イベントの自作

イベントを自作するときには、以下のようにgetHandlers()getHandlerList()を実装する必要があります。
getHandlerList()はEventクラスに含まれていないものの、Javadocには必ず実装するように書いてあります。

All events require a static method named getHandlerList() which returns the same HandlerList as getHandlers().

ビルドは通ってしまいますが、実装しないと実行時に落ちてしまいます。

Java
private static final HandlerList handlers = new HandlerList();

public HandlerList getHandlers() {
    return handlers;
}

public static HandlerList getHandlerList() {
    return handlers;
}

ところが、Kotlinにはstaticメソッドがありません
一応、Companion objectsを用いれば、クラスレベルの関数とプロパティを定義することができます。 しかし、これは本当のstaticメソッド(とフィールド)では無いので、Java側はstaticメソッドと同じように呼び出すことができません。
そこで、@JvmStaticアノテーションを利用します。

@JvmStaticアノテーションを入れると、Companion objects内で定義された関数をstaticメソッドとして生成できます。
すなわち、自作イベントは以下のように記述されます。

Kotlin
package org.example.samplePlugin

import org.bukkit.event.Event
import org.bukkit.event.HandlerList

class SampleEvent(): Event() {
    override fun getHandlers(): HandlerList {
        // 変数名がhandlersのときは
        // SampleEvent.handlersとしないと循環参照になる
        return handlerList
    }

    companion object {
        @JvmStatic
        private val handlerList = HandlerList()

        @JvmStatic
        fun getHandlerList(): HandlerList {
            return handlerList
        }
    }
}

ライブラリの追加

他のGradleを用いた開発と同様に、build.gradle.ktsを編集することでライブラリを追加することができます。

変更したら、右上に象のマークが現れるので、それをクリックして変更を反映します。

ビルド

サイドバーにあるGradleをクリックして、その後[プラグイン名]/Tasks/build/build(画像右側の選択部分)をダブルクリックしてビルドします。
ビルドが完了すると、プロジェクト内にbuildというディレクトリが作成され、その中のlibsの中に[プラグイン名]-[バージョン].jar[プラグイン名]-[バージョン]-all.jarが生成されます。
allの付いている方には依存ライブラリも含まれている(所謂fat JAR)ので、原則こちらを使うようにしましょう。

さいごに

モダンな言語を利用する方が、開発も非常にやりやすく、そして恐らく安全です。
Minecraftサーバーのプラグインを開発する際は、Kotlinを使うことも検討してみてはいかがでしょうか。

GitHubで編集を提案

Discussion