Kotlinで簡単なModを作るまで

以前あまりに抽象的な記事を書いてしまったので(=> https://qiita.com/Pctg-x8/items/b6536cdbe5a1656585f2 )今回は具体的にModを作ってみたいと思います。
今回は、経験値を消費して土を凍らせる魔法の杖を追加するMod「FrostyRod」を作っていきたいと思います。あまり実用的なModではないんですが解説に使ったコード/リソースはPublic Domainとしておくので自由に改変したり一部切り取ったりそのままパッケージ化して配布したりしてもらってもかまいません。

## 環境構築

今回はKotlinを使って書いていきます。

### 1. IntelliJ IDEAの準備  

Community Editionで大丈夫です。Arch Linuxの場合は

> \# pacman -S intellij-idea-community-edition

で入手できます。

### 2. Minecraft Forgeの準備

今回はせっかくなので1.12.2向けのModを作っていきます。http://files.minecraftforge.net/ で1.12.2向けのMdkを入手します。執筆時点では14.23.1.2555が最新のRecommendedです。
ファイルはzipになっているので任意の場所に展開します。

### 3. ワークスペースの準備

zipを展開したそのままの状態ではコンパイル時に要求されるMinecraftのソースなどが存在していないため、その準備を追加で行う必要があります。
このとき、展開したフォルダをそのまま開発に使うのではなく別にMod専用のフォルダを作ってそこにファイルをコピーして使うことをおすすめします。

ファイルのコピーが完了したら以下のコマンドを実行します。

> gradle setupDecompWorkspace

Gradleをインストールしていない場合はForgeに付属のGradleを使うことができます。上記`gradle`を`gradlew`(Unix系の場合は`./gradlew`)に置き換えてください。

上記コマンドが完了したらIntelliJ IDEAを起動し、Import Projectを選択してフォルダ直下のbuild.gradleを指定します。次に出てくるウィンドウはよくわからなければそのままOKで大丈夫です(Auto importだけ有効にしておくと便利かもしれない)。
インポートが完了したらIDEAを閉じ、次のコマンドを実行します。

> gradle genIntellijRuns

ここまで完了したらとりあえずIDEAから起動できると思います。IDEAを起動し、`Run>Run...(Alt+Shift+F10)`を選択後`Minecraft Client`を選択します。ここで選択したあとは`Run>Run 'Minecraft Client'(Shift+F10)`ですぐに起動できるようになります。

### 4. 言語プラグインを入れる

Kotlinを利用するために、もう少しだけ設定が必要です。
まずはIDEAにKotlinのプラグインを入れましょう。`File>Settings...`で出るウィンドウの`Plugins>Install JetBrains plugin...`で入手できます。
それから、`Tools>Kotlin>Configure Kotlin in Project`を実行してください。

## コーディング はじめの一歩

それではコーディングを始めていきましょう。まずは`src/main/java`フォルダが不要なので消します。次に`src/main`直下に`kotlin`フォルダを作り、そこに`main.kt`ファイルを作成します。ファイル名はJavaと違って特に制限はないので、いかにもエントリポイントって感じの名前であればなんでも大丈夫です。
生成されたら、内容を以下のようにします。

```kotlin:main.kt
package com.cterm2.frostyrod

import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.FMLInitializationEvent

@Mod(modid = FrostyRod.ID, name = FrostyRod.Name, version = FrostyRod.Version, modLanguage = "kotlin")
class FrostyRod
{
    companion object
    {
        const val ID = "frostyrod"
        const val Name = "FrostyRod"
        const val Version = "1.0.1"
    }
}
```

import節は補完を使うと勝手にいい感じにしてくれるので、package節(なくてもいい)を書いたあとはすぐに@ Mod〜を書き始めてしまって大丈夫です。
書いてる最中に赤文字の部分が出てきてしまっても、しばらくじっとしてるとAlt+Enterでimportを追加できる旨が表示されるのでそれに従えば基本大丈夫です。
Mod.modLanguageは、Scalaの場合にはここを適切に設定することでエントリポイントをobjectにできたりしてちょっと嬉しいとかあるんですがKotlinでは特に恩恵がないのでここは指定しなくても大丈夫です。

これで実行すると画面には特に変化はありませんが、特にエラーなどが出ていなければ、あと左下に"5 mods active"とあればとりあえず大丈夫です。

## アイテムを追加する

次はアイテムを追加します。新しくファイルを作成し、以下の内容としてください。

```kotlin:item.kt
package com.cterm2.frostyrod

import net.minecraft.item.Item

object ItemRod : Item()
{
    init
    {
        this.maxStackSize = 1
        this.unlocalizedName = "rod"
        this.registryName = ResourceLocation(FrostyRod.ID, "item_rod")
    }
}
```

Kotlin最大の特徴として、特定の命名規則のメソッドが変数と同じ記法で呼び出せるようになるというものがあります。`this.maxStackSize = 1`はJavaでは`this.setMaxStackSize(1)`と全く同じものとなります。
今回の魔法の杖はスタックできないようにしたいので最大スタックサイズを1に設定しています。これはコンストラクタに書く必要があるのですが、Kotlinではinitブロックを用いて記述します。
ついでなので`unlocalizedName`も設定します。Minecraftを日本語にして遊んでいる方はもうご存知かと思いますが、Minecraftには言語設定があり、Mod/ResourcePack側で適切に設定すると表示名を変えることが可能です。Modでは多言語対応は**ほぼ必須**なので忘れずに設定しましょう。
実際の表示名は`src/main/resources/assets/{modのID}/lang/*.lang`で設定されます。最低限英語(US)向けの言語ファイルを提供しておけば大丈夫です。今回のmodの場合は以下のようになります。

```:src/main/resources/assets/frostyrod/lang/en_us.lang
item.rod.name=Frosty Rod
```

`item.{unlocalizedName}.name={表示名}`で指定できます。
registryNameはセーブデータ上のIDとレジストリ(後述)を関連付けるためのもので、あと見た目の解決などにも使われます。"item_rod"がmod内でのアイテム固有の名前である必要があります。

アイテムを追加したら登録を行います。main.ktに戻り、以下の内容を追記してください。

```kotlin:main.kt
// @Modのすぐ下
@Mod.EventBusSubscriber

        // companion object内
        private val ctab = object : CreativeTabs("frostyRod")
        {
            override fun getTabIconItem() = ItemStack(Items.STICK)
        }

        @SubscribeEvent @JvmStatic
        fun register(e: RegistryEvent.Register<Item>) {
            e.registry.register(ItemRod.setCreativeTab(ctab))
        }
```

いつのまにか登録周りがほとんどイベントベースになっていて若干迷いましたが、これでアイテムを登録することが可能です。`EventBusSubscriber`なクラスの`SubscribeEvent`がついた`(RegistryEvent.Register<T>) -> Unit`なメソッドが`T`のサブクラスのオブジェクトの登録フェーズで呼び出されます。Tは`Item`だったり`Block`だったりします。
クリエイティブモードでアイテムを取り出せるようにするには`setCreativeTab`でタブを設定する必要があります。既存のタブか、独自でタブを定義して設定しましょう。

クリエイティブタブの名前もローカライズの対象です。忘れずに指定しましょう。

```:src/main/resources/assets/frostyrod/lang/en_us.lang
itemGroup.frostyRod=Frosty Rod
```

ここまででとりあえずこんな感じになっていると思います。

![2017-12-08_16.09.55.png](https://qiita-image-store.s3.amazonaws.com/0/94115/6def5a90-8e19-82d6-7782-bd114fa72133.png)

## アイテムの見た目の設定

このまま機能を作り込んでもいいのですが、先にアイテムの見た目を設定します。
今回はこのアイコンをアイテムに適用します。16x16であればなんでも大丈夫です。
![rod.png](https://qiita-image-store.s3.amazonaws.com/0/94115/38d27d13-3476-8393-06ca-c555c3551a1a.png)

画像ファイルは`src/main/resources/assets/{modのID}/textures`以下に配置されます。
続いてモデルファイルの定義です。これは`src/main/resources/assets/{modのID}/models`以下に配置され、アイテム向けは`item`フォルダの中になります。ここでのファイル名はアイテムのregistryNameと同じである必要があり、また**全て小文字**となります。

```json:src/main/resources/assets/frostyrod/models/item/item_rod.json
{
  "parent": "item/generated",
  "textures":
  {
    "layer0": "frostyrod:items/rod"
  },
  "display":
  {
    "thirdperson":
    {
      "rotation": [0, 90, -35],
      "translation": [0, 1.25, -3.5],
      "scale": [0.85, 0.85, 0.85]
    },
    "firstperson":
    {
      "rotation": [0, -135, 25],
      "translation": [0, 4, 2],
      "scale": [1.7, 1.7, 1.7]
    }
  }
}
```

モデルファイルの定義は複雑なのでとりあえずhttps://bedrockminer.jimdo.com/modding-tutorials/basic-modding-1-8/model-files/ を参考にするなりするといいと思います。`parent`は`builtin/generated`で解説されているところが多いですが`item/generated`にしないと回転が若干おかしい感じになります。
`textures`でテクスチャを指定します。先頭にmodのID名、その後ろに:を挟んで`textures`以下のパス(拡張子抜き)を指定します。

モデルファイルが用意できたらコードの方で読み込みを行います。

```kotlin:main.kt
        @SubscribeEvent @JvmStatic
        fun registerModels(e: ModelRegistryEvent) {
            ModelLoader.setCustomModelResourceLocation(ItemRod, 0, ModelResourceLocation(ItemRod.registryName!!, "inventory"))
        }
```

`ModelResourceLocation`のコンストラクタの第一引数が`NotNull`を要求してくるので!!で明示しています。
ここまででアイテムに画像がついてよりそれっぽくなったと思います。

## 右クリックのアクションを設定する

右クリックでのアクションの設定は2通り、`onItemUse`と`onItemRightClick`があります。前者はブロックに対してアイテムを使った際に呼び出され、後者はアイテムを持った状態で右クリック(正確には右ボタンを押下)すると呼び出されます。今回はブロックに対してのアクションを指定したいので`onItemUse`を使います。

```kotlin:item.kt
    override fun onItemUse(player: EntityPlayer?, worldIn: World?, pos: BlockPos?, hand: EnumHand?,
                           facing: EnumFacing?, hitX: Float, hitY: Float, hitZ: Float): EnumActionResult {
        worldIn?.let { w ->
            if(!w.isRemote && pos != null)
            {
                val b = w.getBlockState(pos).block
                if(b == Blocks.GRASS || b == Blocks.DIRT) player?.let { this.freeze(it as EntityPlayerMP, w as WorldServer, pos) }
            }
        }
        return EnumActionResult.SUCCESS
    }
```

`player`はわかりませんが、`worldIn`と`pos`が`null`になることはまずないはずなので`let`じゃなくて!!で外してしまっても大丈夫です。
アイテム使用時に対象のブロックを取得し、草もしくは土ブロックであった場合に凍らせるアクション(freezeメソッド呼び出し)を行っています。サーバーワールドの場合(`!w.isRemote`で判定しています)、`player`は`EntityPlayerMP`に、`w(worldIn)`は`WorldServer`になるのでキャストを行っています。

freezeアクションの内容は以下のようになっています。

```kotlin:item.kt
    private const val CostExp = 1
    private fun freeze(player: EntityPlayerMP, w: WorldServer, pos: BlockPos)
    {
        if(player.capabilities.isCreativeMode || player.experienceLevel >= CostExp)
        {
            // consuming user's experience in survival/hardcore mode
            if(!player.capabilities.isCreativeMode) player.addExperienceLevel(-CostExp)
            w.spawnParticle(EnumParticleTypes.EXPLOSION_NORMAL, true,
                    pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, 20, 0.0, 0.0, 0.0, 0.1)
            w.setBlockState(pos, Blocks.ICE.defaultState, 3)
        }
    }
```

クリエイティブモードの場合は無制限に、それ以外の場合は経験値レベルを消費して使えるようにしています。消費量は`CostExp`で調整できるようになっています。
ブロックを変更する際、末尾に3を指定するとブロックの変更イベントとクライアントへのブロックの変更通知が発生するようになります。通常はこれで十分なため3以外を指定することは殆ど無いです。
ブロックを置き換える際に爆発パーティクルを表示するようにしています。これは特に説明はいらないでしょうが、ブロック座標に0.5を足してブロックの中心から発生するようにしています。

![完成図](https://qiita-image-store.s3.amazonaws.com/0/94115/cd024f8b-1528-1417-0fad-60e8841ff143.png)

わーい

## さいごに

以上で一通り完成ですが、最後にひとつだけ、`mcmod.info`の編集についても書いておきます。

```json:mcmod.info
[
{
  "modid": "frostyrod",
  "name": "FrostyRod",
  "description": "Example mod",
  "version": "${version}",
  "mcversion": "${mcversion}",
  "url": "",
  "updateUrl": "",
  "authorList": ["S.Percentage"],
  "credits": "",
  "logoFile": "",
  "screenshots": [],
  "dependencies": []
}
]
```

FrostyRodのmcmod.infoはこんな感じになっています。
これは何かというと、タイトル画面でModsを選んだ際に出る画面に表示される内容になっています。

![modinfo](https://qiita-image-store.s3.amazonaws.com/0/94115/b0ded92a-df92-d1b2-c4dc-c7cafdfdceca.png)

ここを適切に指定することで、バグレポートやアップデート配信場所などの情報をユーザーにしめすことができます(あんまり見る人はいないと思いますが)。