Minecraft Modding環境をgradle.kts・kotlinにする試み◆[forge 1.12.2][gradle 7.1.1][kotlin]

# あらまし

- 執筆時点でほぼ最新のKotlin版GradleとForgeGradleプラグインを使って、Minecraft 1.12.2版のForge Modding環境を作った
- ソースコードにJavaとKotlinのファイルを混在して配置できる
- IntelliJでクライアントとサーバーの起動とHotSwapのテストができた
- 成果物の出力関連はノータッチ
- →だめだった
    - バージョンをうまく調整するといけそう

# 背景

手元のプロジェクトをガンガンモダンなGradleに切り替えて、プログラミング言語もJavaからKotlinに移行しているので、

https://qiita.com/MirrgieRiana/items/59b4596461b0c77208c9

この際なのでついにビルドスクリプトが初期状態でgroovy版Gradleの状態で提供されているMinecraftのModding環境もbuild.gradle.ktsに移行しました。

この記事はその際のメモです。

# 目標

扱う

- Gradleを更新する
- ビルドスクリプトをKotlin版Gradleに切り替える
- ForgeGradleプラグインを更新する
- ModのソースコードをJava・Kotlin混合状態にする
- IntelliJでコードのホットスワップができるようにする

まだ扱わない

- Javaバージョン指定
- テストコード
- jarの生成
- shadow jarでkotlinの依存関係を含める
- shadow jarの添付ライブラリのパッケージ名を変更する
- CIによる自動ビルド

# 対象読者

この記事は次の知識がある前提で書かれています。

- デフォルトのGroovy版Gradleを使用したMinecraft Forge Modding
- EclipseでのJavaでの開発経験

この記事は次の知識がない前提で書かれています。

- Kotlin版Gradleを使用したMinecraft Forge Modding
- IntelliJでのHotSwapの利用

# 環境

- コンパイル対象
    - Minecraft 1.12.2
    - Minecraft Forge 14.23.5.2855
    - Kotlin 1.5.20
- ビルドスクリプト
    - Gradle 7.1.1
    - ForgeGradle 5.1.11
- 開発環境
    - Windows 10
    - IntelliJ IDEA 2021.1.3 (Community Edition)

# 手順

## Minecraft ForgeのModding環境のダウンロード

とりあえず問題のForgeのModdingキットを入手します。
2種類のビルドスクリプトを入手して、両方に含まれる情報をマージしたうえでさらにGradleのバージョン移行もするのが目標です。

### 1.12.2 - 14.23.5.2855

これは構築対象のModding環境です。

https://files.minecraftforge.net/net/minecraftforge/forge/index_1.12.2.html

`forge-1.12.2-14.23.5.2855-mdk.zip`

----

中身はこのような感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/af73dd29-c28e-679e-b574-6d4e801d6181.png)

### 1.17.1 - 37.0.9

これは現時点で最新のModding環境です。
これにはモダンなForgeGradleプラグインの扱い方の情報が含まれています。

https://files.minecraftforge.net/net/minecraftforge/forge/index_1.17.1.html

`forge-1.17.1-37.0.9-mdk.zip`

中身はこのような感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/eee6eaad-6f46-9778-f7fc-39b2777f3f51.png)

### ビルドに関わるファイル

ビルドに関わる部分は次のものです。

- `gradle` ---- Gradleラッパーのバージョンごとに固定
- `src` ---- ソースコードの構成は従来の知識で扱える
- `gradle.properties` ---- GradleのVM設定しか書かれていない
- `gradlew` ---- Gradleラッパーのバージョンごとに固定
- `gradlew.bat` ---- Gradleラッパーのバージョンごとに固定
- `build.gradle` ---- 複雑

このうち本当に問題が複雑なのは`build.gradle`のみです。

## IntelliJ IDEA Community版のダウンロード

今回はWindows上でIntelliJを使うので、それを用意します。

https://www.jetbrains.com/ja-jp/idea/download/#section=windows

多分exeでもzipでも同じことができると思います。

----

【余談】実は当初Android StudioをIntelliJの代用にしていましたが、再コンパイルしてもなぜか`Loaded classes are up to date. Nothing to reload`と出るだけでHotSwapが利かず（HotSwapしようとはしているけどなぜか差分が見つからない？）、あれこれキャッシュ破棄などを繰り返している過程で、Community版にしたらいつの間にか効くようになっていました（謎）（TODO）。

**参考資料**

https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000110004-HotSwap-Loaded-classes-are-up-to-date-Nothing-to-reload-on-PC-not-on-laptop

## 1.12.2版と最新版での`build.gradle`の差分のチェック


以下は1.12.2のビルドスクリプトからコメントを省いたものです。

<details><summary>[+]</summary><div>

```gradle
buildscript {
    repositories {
        maven { url = 'https://files.minecraftforge.net/maven' }
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'net.minecraftforge.gradle:ForgeGradle:3.+'
    }
}

apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'

version = '1.0'
group = 'com.yourname.modid'
archivesBaseName = 'modid'

sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.

minecraft {
    mappings channel: 'snapshot', version: '20171003-1.12'
    runs {
        client {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
        server {
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
    }
}

dependencies {
    minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855'
}

jar {
    manifest {
        attributes([
            "Specification-Title": "examplemod",
            "Specification-Vendor": "examplemodsareus",
            "Specification-Version": "1", // We are version 1 of ourselves
            "Implementation-Title": project.name,
            "Implementation-Version": "${version}",
            "Implementation-Vendor" :"examplemodsareus",
            "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

jar.finalizedBy('reobfJar') 

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        maven {
            url "file:///${project.projectDir}/mcmodsrepo"
        }
    }
}
```

</div></details>

以下は1.17.1のビルドスクリプトからコメントを省いたものです。

<details><summary>[+]</summary><div>

```gradle
buildscript {
    repositories {
        maven { url = 'https://maven.minecraftforge.net' }
        mavenCentral()
    }
    dependencies {
        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
    }
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'

version = '1.0'
group = 'com.yourname.modid'
archivesBaseName = 'modid'

java.toolchain.languageVersion = JavaLanguageVersion.of(16)

println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
minecraft {
    mappings channel: 'official', version: '1.17.1'
    runs {
        client {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'REGISTRIES'
            property 'forge.logging.console.level', 'debug'
            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }
        server {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'REGISTRIES'
            property 'forge.logging.console.level', 'debug'
            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }
        data {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'REGISTRIES'
            property 'forge.logging.console.level', 'debug'
            args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }
    }
}

sourceSets.main.resources { srcDir 'src/generated/resources' }

repositories {
}

dependencies {
    minecraft 'net.minecraftforge:forge:1.17.1-37.0.9'
}

jar {
    manifest {
        attributes([
                "Specification-Title"     : "examplemod",
                "Specification-Vendor"    : "examplemodsareus",
                "Specification-Version"   : "1",
                "Implementation-Title"    : project.name,
                "Implementation-Version"  : project.jar.archiveVersion,
                "Implementation-Vendor"   : "examplemodsareus",
                "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

jar.finalizedBy('reobfJar')

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        maven {
            url "file://${project.projectDir}/mcmodsrepo"
        }
    }
}
```

</div></details>

以下は差分です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/f13a245f-bb37-6218-70af-ebd2763cb8d1.png)

### Forgeのリポジトリの変更

```diff_gradle
     repositories {
-        maven { url = 'https://files.minecraftforge.net/maven' }
-        jcenter()
+        maven { url = 'https://maven.minecraftforge.net' }
         mavenCentral()
     }
```

ForgeのMavenリポジトリのURLが
`https://files.minecraftforge.net/maven`から`https://maven.minecraftforge.net`に変わっています。

古い方にアクセスすると新しい方にリダイレクトされるので、とりあえず新しい方を指定しておけばよいでしょう。

`jcenter`は非推奨になったので消えました。

### ForgeGradleバージョンの更新

```diff_gradle
     dependencies {
-        classpath 'net.minecraftforge.gradle:ForgeGradle:3.+'
+        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
     }
```

使用するForgeGradleプラグインのバージョンが`3.+`から`5.1.+`に変わりました。
今回は`5.1.11`固定にしたいため、ここは`5.1.11`にします。

----

`changing: true`は、常に最新版を使いたい場合に指定するようです。

https://docs.gradle.org/current/userguide/dynamic_versions.html

### Javaバージョンの指定

```diff_gradle
-sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8'
+java.toolchain.languageVersion = JavaLanguageVersion.of(16)
```

新旧でJavaバージョンが1.8→16に変わっているのと、記法そのものが変わっています。

今回はとりあえず動けばいいので古い方のまま使います。

### デバッグ出力の追加

```diff_gradle
+println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
```

これはログの出力しかしてないので省きます。

### Minecraft Mappingバージョンの更新

```diff_gradle
-    mappings channel: 'snapshot', version: '20171003-1.12'
+    mappings channel: 'official', version: '1.17.1'
```

これはアルファベットで書かれた難読化・最小化された短い識別子名と、英単語で書かれた可読性のある長い識別子名の対応を記した定義ファイルです。
Forgeのバージョンごとに固定でよいと思うので、古い方を使います。

原文のコメントを読むと、officialの方にはライセンス上の何かがあるっぽいです。

https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md

### 実行構成の更新

```diff_gradle
     runs {
         client {
             workingDirectory project.file('run')
-            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
+            property 'forge.logging.markers', 'REGISTRIES'
             property 'forge.logging.console.level', 'debug'
+            mods {
+                examplemod {
+                    source sourceSets.main
+                }
+            }
         }
         server {
-            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
+            workingDirectory project.file('run')
+            property 'forge.logging.markers', 'REGISTRIES'
+            property 'forge.logging.console.level', 'debug'
+            mods {
+                examplemod {
+                    source sourceSets.main
+                }
+            }
+        }
+        data {
+            workingDirectory project.file('run')
+            property 'forge.logging.markers', 'REGISTRIES'
             property 'forge.logging.console.level', 'debug'
+            args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
+            mods {
+                examplemod {
+                    source sourceSets.main
+                }
+            }
         }
     }
```

変更点は、以下の通りです。

- `forge.logging.markers`が変わった
- `server`側に`workingDirectory`の指定が増えた
- `mods`節が増えた
- `data`節が増えた

ロギング関係は実行そのものには影響はないので古い方を使います。

`server`側の`workingDirectory`の指定は、デフォルトの値を再指定しているだけなので本来は不要のはずですが、折角増えているのでこちらを採用します。
クライアントもサーバーも同じ`run`ディレクトリを指定することができますが、ファイルがごちゃまぜになって面倒だしサーバー側をリセットしたいときにクライアント側もまとめて消えるのが面倒なので別のディレクトリを指定しようと思います。

`mods`節が増えていますが、よくわからない（TODO）ので古い方に合わせます。
指定しなくても動いたということは1.12.2版のForgeでは必要ない機能である可能性があります。

`data`節はリソースの自動生成に関するもののようです。
jsonファイルなどを自力で書けば関係ないので丸ごと省きます。

`runs`節はGradle上からMinecraftアプリケーションの実行を行わない場合（例えばビルドしか行わない場合など）は丸ごと省くことができます。

### リソースディレクトリの追加

```diff_gradle
+sourceSets.main.resources { srcDir 'src/generated/resources' }
```

自動生成されたリソースのディレクトリ（？）を追加しています。
リソースの自動生成は今回使用しないので省きます。

### 依存性の変更

```diff_gradle
+repositories {
+}
+
 dependencies {
-    minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855'
+    minecraft 'net.minecraftforge:forge:1.17.1-37.0.9'
 }
```

依存性の変更はForgeバージョンの変更のみです。
古い方を使います。

### Jarマニフェストの更新

```diff_gradle
 jar {
     manifest {         attributes([
             "Specification-Title": "examplemod",
             "Specification-Vendor": "examplemodsareus",
             "Specification-Version": "1",
             "Implementation-Title": project.name,
-            "Implementation-Version": "${version}",
+            "Implementation-Version": project.jar.archiveVersion,
             "Implementation-Vendor" :"examplemodsareus",
             "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
         ])
     }
 }
```

`Implementation-Version`が`version`から`project.jar.archiveVersion`を参照するように変わりました。
後者でよいと思います。

### アップロード先パスの修正

```diff_gradle
     repositories {
         maven {
-            url "file:///${project.projectDir}/mcmodsrepo"
+            url "file://${project.projectDir}/mcmodsrepo"
         }
     }
```

`project.projectDir`には絶対パスが入っているため`/`の個数が余計になることの修正？
後者を使います。

## `build.gradle`のマージ

二つのビルドスクリプトをマージしました。

<details><summary>[+]</summary><div>

```gradle
buildscript {
    repositories {
        maven { url = 'https://maven.minecraftforge.net' }
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.11'
    }
}

apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'

version = '1.0'
group = 'com.yourname.modid'
archivesBaseName = 'modid'

sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.

minecraft {
    mappings channel: 'snapshot', version: '20171003-1.12'
    runs {
        client {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
        server {
            workingDirectory project.file('runServer')
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
    }
}

dependencies {
    minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855'
}

jar {
    manifest {
        attributes([
            "Specification-Title": "examplemod",
            "Specification-Vendor": "examplemodsareus",
            "Specification-Version": "1", // We are version 1 of ourselves
            "Implementation-Title": project.name,
            "Implementation-Version": project.jar.archiveVersion,
            "Implementation-Vendor" :"examplemodsareus",
            "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

jar.finalizedBy('reobfJar') 

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        maven {
            url "file://${project.projectDir}/mcmodsrepo"
        }
    }
}
```

</div></details>

## Gradle Wrapper 7.1.1の入手

以下の記事に従ってgradlewを手に入れ、既存の`gradle` `gradlew` `gradlew.bat`を上書きします。

https://qiita.com/MirrgieRiana/items/59b4596461b0c77208c9

## キャッシュの破棄

`%HOMEPATH%`の下にある`.m2`と`.gradle`を削除します。
これによってGradleを次に使用したときに大量の通信が発生します。

プロジェクトディレクトリに`.idea`がある場合は、これを削除します。
これによってIntelliJ上で行ったプロジェクトごとの設定が初期化されます。

これはModのコンパイル時にJavaバージョンの異なる環境でコンパイルしたMinecraftがエラーを起こす問題の対策です。

## プロジェクトをIntelliJ IDEAで開いてみる

マージ後の`build.gradle`とGradle 7.1.1を使用したプロジェクトは普通に開けました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/ed56ed92-7287-d16d-1d0c-99a1befae2a9.png)

ForgeGradleはGradleのバージョンに依存性があります。
古いForgeGradleは古いGradle、新しいForgeGradleは新しいGradleでしか動きません。

### クライアントを起動してみる

`runClient`タスクを実行します。
このタスクは`runs`節内で定義された`client`に由来するものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/b3e86c67-048e-f43f-39d5-b1c7fe4be77c.png)

無事実行できるとチャット内容がIDE上に現れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/35f17f14-f400-cb60-55c6-f2a89eb0501c.png)

----

Forge MDKにはデフォルトでサンプルMODが用意されていますが、これがちゃんと動いていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/29b31507-c1ae-cf30-b991-78cfe909b325.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/151052e6-4a09-0402-2c73-dcaa7261f57c.png)

### `java.nio.ByteBuffer.flip`エラー

当初ここでエラーに遭遇しました。

`java.nio.ByteBuffer.flip`がどうとか言われる場合は、Minecraftクライアントをビルドした時点でのJavaバージョンと`runClient`時に使われるJavaバージョンがあっていません。
ビルドされたMinecraftは`%HOMEPATH%\.gradle`内に存在するようです。

その場合、次の場所をすべて1.8に統一したあとでIntelliJを閉じてキャッシュを削除し、再び起動を試みます。

----

`ファイル→プロジェクト構成→プロジェクト設定→プロジェクト→プロジェクトSDK` = `corretto-1.8`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/cb8c04c5-5b38-f168-a944-01deac61d39f.png)

ここでWSL版のjavaがリストにある場合がありますが、それを使用すると別のエラーが出ます。

----

`ファイル→設定→ビルド、実行、デプロイ→ビルドツール→Gradle→Gradle JVM` = `プロジェクト SDK 1.8`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/6bbeddd9-be50-6143-ce00-50c4efb2796c.png)

----

`ファイル→プロジェクト構成→プロジェクト設定→モジュール→モジュールSDK` = `プロジェクト SDK`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/8a2163dc-1cdc-6559-d3b4-914b073ccee9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/852daa1c-b7ac-ccd0-7349-190f30695c1f.png)

----

`実行構成の編集→（ここに何かある場合はそこ）`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/714cb3f4-c2b3-32a7-6b7d-88c4fccd4bff.png)

Gradleタスクの実行構成にはJavaのバージョンを選択するUIはありませんでした。

## `build.gradle.kts`への移行

この時点で新しいGradleで、新しいForgeGradleプラグインで、1.12.2版のForgeで、正常に動作するところまで来ました。

次はビルドスクリプトの言語をGroovyからKotlinに移行する最も大きな仕事です。

### Gradleプラグインの指定

`build.gradle`においてGradleプラグインに関係する場所は以下の部分です。

```gradle
buildscript {
    repositories {
        maven { url = 'https://maven.minecraftforge.net' }
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.11'
    }
}

apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
```

問題を簡単にするために、今回は使わない`eclipse`プラグインと`maven-publish`プラグインは外すことにします。
非推奨となった`jcenter()`リポジトリも除外します。

```gradle
buildscript {
    repositories {
        maven { url = 'https://maven.minecraftforge.net' }
        mavenCentral()
    }
    dependencies {
        classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.11'
    }
}

apply plugin: 'net.minecraftforge.gradle'
```

残ったのはForgeGradleのプラグインを使用するためのものだけです。
これはKotlin版Gradleでは次のように書きます。

```kotlin:build.gradle.kts
plugins {
    id("net.minecraftforge.gradle")
}
```

```kotlin:settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        maven("https://files.minecraftforge.net/maven")
    }
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "net.minecraftforge.gradle" -> useModule("net.minecraftforge.gradle:ForgeGradle:5.1.11")
            }
        }
    }
}
```

`build.gradle.kts`側には使用するプラグインのID（およびバージョン）のみを記述して、`settings.gradle.kts`側でプラグインの解決方法を定義します。

`plugins`節は一つの`build.gradle.kts`内に1個だけ記述できます。
新たなGradleプラグインを追加したい場合は、既存の`plugins`節に追記します。

----

【余談】`eachPlugin`節を記述すると、明示的に解決方法が指定されていないプラグインの解決に失敗するようになります。明示的に解決方法を定義しない場合にデフォルトの解決方法を使用するような記述は不明です（TODO）。

### 成果物に関する指定

この辺は主にjarファイルを生成する際に関わってくるもので、今回は実行までで妥協するためばっさり省略します。

```gradle
version = '1.0'
group = 'com.yourname.modid'
archivesBaseName = 'modid'
```

```gradle
jar {
    manifest {
        attributes([
            "Specification-Title": "examplemod",
            "Specification-Vendor": "examplemodsareus",
            "Specification-Version": "1", // We are version 1 of ourselves
            "Implementation-Title": project.name,
            "Implementation-Version": project.jar.archiveVersion,
            "Implementation-Vendor" :"examplemodsareus",
            "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

jar.finalizedBy('reobfJar') 

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        maven {
            url "file://${project.projectDir}/mcmodsrepo"
        }
    }
}
```

### Javaバージョンの指定

次の記述は、Kotlin版では次のようにしました。

```gradle
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
```

↓

```kotlin
tasks.withType<JavaCompile> {
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    options.encoding = "UTF-8"
}
```

無修飾の`sourceCompatibility`や`targetCompatibility`がどこの実体を指しているのかは不明です（TODO）。

### `minecraft`節

Kotlinでもほとんど同じように記述できます。

```gradle
minecraft {
    mappings channel: 'snapshot', version: '20171003-1.12'
    runs {
        client {
            workingDirectory project.file('run')
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
        server {
            workingDirectory project.file('runServer')
            property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
            property 'forge.logging.console.level', 'debug'
        }
    }
}
```

↓

```kotlin
minecraft {
    mappings("snapshot", "20171003-1.12")
    runs {
        create("client") {
            workingDirectory(project.file("run"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
        create("server") {
            workingDirectory(project.file("runServer"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
    }
}
```

ここには大きなハマりポイントがありました。
`client { }`は`create("client") { }`と翻訳します。
`register("client")`だと初期化が正しく行われず、`runClient`中にプロパティの欠落で異常終了しました。

### 依存性の定義

ここもセオリー通りです。

```gradle
dependencies {
    minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855'
}
```

↓

```kotlin
dependencies {
    minecraft("net.minecraftforge:forge:1.12.2-14.23.5.2855")
}
```

`minecraft`関数はForgeGradleプラグインが提供してくれるため、関数として呼ぶことができます。

## できたビルドスクリプト

Kotlin版のビルドスクリプトができました。
成果物に関係する記述がないため、現状Moddingをしてもその場で実行しかできません。

```kotlin:build.gradle.kts
plugins {
    id("net.minecraftforge.gradle")
}

tasks.withType<JavaCompile> {
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    options.encoding = "UTF-8"
}

minecraft {
    mappings("snapshot", "20171003-1.12")
    runs {
        create("client") {
            workingDirectory(project.file("run"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
        create("server") {
            workingDirectory(project.file("runServer"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
    }
}

dependencies {
    minecraft("net.minecraftforge:forge:1.12.2-14.23.5.2855")
}
```

```kotlin:settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        maven("https://files.minecraftforge.net/maven")
    }
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "net.minecraftforge.gradle" -> useModule("net.minecraftforge.gradle:ForgeGradle:5.1.11")
            }
        }
    }
}
```

`build.gradle`は削除します。

## ビルドスクリプトをIntelliJ IDEAで読み込んでみる

上手くいくとIntelliJ上で`minecraft`関数が関数として認識されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/515c106e-fac3-dd09-912e-ec21f7bd1e25.png)

### `runClient`の実行

この構成で普通に起動できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/40cac190-c073-5447-8186-b33d7b4b17b5.png)

## ModソースにKotlinコードを含められるようにする

Kotlinをコンパイルできるようにするための記述は非常に簡潔です。

----

`plugins`節でKotlin JVMのGradleプラグインの使用を定義する。

```kotlin
    kotlin("jvm")
```

----

`dependencies`節でJVM版Kotlinのコンパイル・実行時に必要なライブラリへの依存性を定義する。

```kotlin
    implementation(kotlin("stdlib-jdk8", "1.5.20"))
```

----

`eachPlugin`節でKotlin JVMのGradleプラグインの解決方法を定義する。

```kotlin
                "org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
```

### できたもの

```kotlin:build.gradle.kts
plugins {
    kotlin("jvm")
    id("net.minecraftforge.gradle")
}

tasks.withType<JavaCompile> {
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    options.encoding = "UTF-8"
}

minecraft {
    mappings("snapshot", "20171003-1.12")
    runs {
        create("client") {
            workingDirectory(project.file("run"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
        create("server") {
            workingDirectory(project.file("runServer"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
    }
}

dependencies {
    implementation(kotlin("stdlib-jdk8", "1.5.20"))
    minecraft("net.minecraftforge:forge:1.12.2-14.23.5.2855")
}
```

```kotlin:settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        maven("https://files.minecraftforge.net/maven")
    }
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
                "net.minecraftforge.gradle" -> useModule("net.minecraftforge.gradle:ForgeGradle:5.1.11")
            }
        }
    }
}
```

これで`src/main/java`内にKotlinソースを書けるようになりました。

### ビルドスクリプトでコンパイル構成を変えた際はIntelliJの設定が変わる場合がある

前述のJavaバージョンが書き換わっていて、Javaバージョンをビルドスクリプト上で明示していてもWSL版Java 8を使いだしたりするので注意が必要です。

## HotSwapの有効化

一旦ここでHotSwapに関する設定に触れます。

----

`ファイル→設定→ビルド、実行、デプロイ→ビルドツール→Gradle→ビルドおよび実行に使用`を`IntelliJ IDEA`に変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/d1d2fe24-9c43-2e5b-9b4a-778f38874a19.png)

これでどう言うわけだかHotSwapが可能になりました。

## Kotlinソースを書いてみる

`ExampleMod.java`を改造して`KotlinClassTest.kt`を呼び出すようにしてみます。

```java:src/main/java/examplemod/ExampleMod.java
package com.example.examplemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

@Mod(modid = "examplemod", name = "Example Mod", version = "1.0")
public class ExampleMod {
    @EventHandler
    public void init(FMLInitializationEvent event) {
        MinecraftForge.EVENT_BUS.register(new Object() {
            @SubscribeEvent
            public void handle(LivingEntityUseItemEvent.Start event) {
                new KotlinClassTest().function(event);
            }
        });
    }
}
```

```kotlin:src/main/java/examplemod/KotlinClassTest.kt
package com.example.examplemod

import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent

class KotlinClassTest {
    fun function(event: LivingEntityUseItemEvent.Start) {
        println("[[[ ${event.item.item.registryName} ]]]")
    }
}
```

これで、弓矢や盾を使用開始した時点でそのアイテムIDがコンソール上に出るMODが出来ました。

### 実行

ついでにHotSwapのテストもするため、デバッグとして実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/181a9637-52d2-6760-b571-08050f951eed.png)

----

書いたとおりの出力がコンソール上に出ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/75f7bd76-de23-3851-4276-c0c21df1df88.png)

### HotSwapのテスト

デバッグモードで実行した`runClient`によるMinecraftクライアントがまだ起動している状態でソースコードを適当に改変して、再コンパイルを明示的に実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/101e28c3-e1d5-6bee-64d8-0e92e3d5ce40.png)

----

上手くいった場合は次のようなポップアップが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/0770c002-bf0b-c708-0c50-07120d53a7f3.png)

----

弓を引き始めたときに表示されるメッセージが改変後のものになりました。
HotSwapは成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/4b8d88c0-fa62-ce03-8a16-6e127c378430.png)

## サーバーを動かす

サーバーサイドの起動とホットスワップのテストも試みます。

この節の内容は次の記事と内容が部分的にかぶっています。

https://qiita.com/MirrgieRiana/items/06f93efffafec224ea91

----

サーバーはここから起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/2ba0a07f-4fdd-bde4-c3cf-82039e4f47b3.png)

### EULAに関する処理

ライセンスに同意していないため、サーバーの起動には初回は必ず失敗します。

> You need to agree to the EULA in order to run the server. Go to eula.txt for more info.

ライセンスに同意するにはここを`true`にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/e2d325da-1e7e-8713-7787-e5ef75416523.png)

### 認証をオフにする

認証をしていない`Player123`のような開発用のプレイヤーがログインするために、`server.properties`で`online-mode`を`false`にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/c76756b4-66b6-c8ea-d0a2-25717738f0c7.png)

### ログインしてみる

これで`runServer`で起動したサーバーに`runClient`で起動したクライアントからログインできるようになりました。

接続先アドレスは`localhost:25565`です。

----

ちゃんとサーバーサイドでもKotlinコードが動作しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/e61bb9a9-6116-767e-4a03-2d62594c54b4.png)

### HotSwapしてみる

ちゃんとソースコードを書き換えて再コンパイルしたらサーバーサイドのログも変わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280627/cb5c49c7-7d8a-33da-79c6-21a3874834c9.png)

これでやりたいことはすべて完了です。

# まとめ

gradlew 7.1.1を手に入れたらコレをコピペして使えばいい。

```kotlin:build.gradle.kts
plugins {
    kotlin("jvm")
    id("net.minecraftforge.gradle")
}

tasks.withType<JavaCompile> {
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    options.encoding = "UTF-8"
}

minecraft {
    mappings("snapshot", "20171003-1.12")
    runs {
        create("client") {
            workingDirectory(project.file("run"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
        create("server") {
            workingDirectory(project.file("runServer"))
            property("forge.logging.markers", "SCAN,REGISTRIES,REGISTRYDUMP")
            property("forge.logging.console.level", "debug")
        }
    }
}

dependencies {
    implementation(kotlin("stdlib-jdk8", "1.5.20"))
    minecraft("net.minecraftforge:forge:1.12.2-14.23.5.2855")
}
```

```kotlin:settings.gradle.kts
pluginManagement {
    repositories {
        mavenCentral()
        maven("https://files.minecraftforge.net/maven")
    }
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
                "net.minecraftforge.gradle" -> useModule("net.minecraftforge.gradle:ForgeGradle:5.1.11")
            }
        }
    }
}
```

IntelliJではGradleタスクをそのまま実行してもちゃんとHotSwapも動作するぞ！

# 追記

この方法では無理ということが判明しました。
バージョンを調整するとこれと近い手法でできるっぽい。
詳細はコメント欄にて。