前までは、Minecraftと外部をやりとりするためにプラグインとAPIサーバ、Webサーバに分けるアプローチをとっていたが、やっぱりこれは 良くないように思える。WebSocketなどを用いた通信なども考えたが、単なる通信のみならそれでも出来そうだが、そのほかのことをするとなると 自分の技術力で実装できるかがわからない。

そこで、既存のフレームワークを用いることにした。 前にSpringBootをプラグインに埋め込むことをもくろんだが、ファイル構成が違ったりしてビルドに難ありという感じだった。 Kotlinという言語特性を活かせるKtorであれば、もしかしたら埋め込めるかもしれないと思った。

ということで実際に試してみる。


すでにMinecraftでは1.19がリリースされているが、今回想定するMinecraftバージョンは1.18.1とする。

とりあえず、いつものようにひな形をつくる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.6.10"
    id ("com.github.johnrengelman.shadow") version "7.1.2"
    application
}

group = "io.github.rokuosan"
version = "1.0.0"
java.sourceCompatibility = JavaVersion.VERSION_17
val mcVersion = "1.18.1"

repositories {
    mavenCentral()
    maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}

dependencies {
    compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
    testImplementation("org.jetbrains.kotlin:kotlin-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}

application {
    mainClass.set("io.github.rokuosan.ktorintegration.AppKt")
}

tasks.withType<KotlinCompile>{
    kotlinOptions{
        jvmTarget = "17"
    }
}

tasks.withType<ProcessResources>{
    val props = mapOf("version" to version)

    inputs.properties(props)
    filteringCharset = "UTF-8"
    filesMatching("plugin.yml"){
        expand(props)
    }
}

ここにKtor の依存関係を追加する。

1
2
3
4
5
6
7
dependencies {
    // Ktor
    implementation("io.ktor:ktor-server-core:2.0.3")
    implementation("io.ktor:ktor-server-netty:2.0.3")

    /* 以下省略 */
}

そして、applicationからメインクラスをセットする。

1
2
3
application {
    mainClass.set("io.github.rokuosan.ktorintegration.AppKt")
}

次に実際にビルドができるかHello Worldで試してみる。

メインクラスに以下のように書き込んで確認する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun main(){
    embeddedServer(Netty, 8080){
        routing {
            get("/"){
                call.respondText("Hello World")
                println("Connection OK")
            }
        }
    }.start(true)
}

これはうまく動いた。

しかし起動時にSLF4Jがないというような警告を受けた。

これは依存関係を追加することで解決できた。

参考: https://ktor.io/docs/logging.html

dependenciesに追記して、

1
implementation("ch.qos.logback:logback-classic:1.2.11")

logback.xmlをresourcesに書く。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="trace">
        <appender-ref ref="STDOUT"/>
    </root>
    <logger name="io.netty" level="INFO"/>
</configuration>

再度起動すると、このような表示に変わっている。


Ktorをビルドすることができたので、次はプラグインに埋め込む。

さっきかいたmain()があるファイルにJavaPluginクラスを継承したクラスを作成する。

onEnable()をオーバーライドする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.bukkit.plugin.java.JavaPlugin

class App: JavaPlugin() {

    override fun onEnable() {
        embeddedServer(Netty, 8080){
            routing {
                get("/"){
                    call.respondText("Hello World")
                    println("Connection OK")
                }
            }
        }.start(true)
    }

最後にプラグインに必要なplugin.ymlをresourcesに作成した。

あとはfatJarとしてビルドする。


最後にMinecraftサーバーを起動する。

パット見成功したように思ったが、これ以降のログがKtorのみになった。

それに加えてMinecraftサーバーが動作しない。

もしかしたら、非同期のスケジューラを使用すれば起動できるかもしれない。


予想通り、非同期のスケジューラとして起動することで並行して処理ができる。

無名クラスとしてスケジューラを実装した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
override fun onEnable() {
    object: BukkitRunnable(){
        override fun run(){
            embeddedServer(Netty, 8080){
                routing {
                    get("/"){
                        call.respondText("Hello World")
                        println("Connection OK")
                    }
                }
            }.start(true)
        }
    }.runTaskAsynchronously(this)
}

デメリットとしては、非同期のスケジューラではSpigotの機能を使用することができないので、Ktor側からMinecraft側の処理は直接行えない。


SpigotプラグインにKtorを埋め込んでみたが、思っていたより簡単だった。

今回作成したプログラムはGitHubに公開した。