複合ツールを作る@Minecraft by Kotlin

#初めに
AdventCalendar、かなり遅れました。
AdventCalendarに投稿する予定だった日、12/22は結月ゆかりの誕生日です。
なんとゆかりさんも5周年。
様々なゆかり楽曲・動画・グッズがあります。
そして、その誕生祭に俺もなにかやりたいと思って作ってたのが、既存のmodの大型アップデート。
そのときに複合ツールを主に追加したのですが、以前からあった複合ツールのコードに他のmodderさん(外国人)に突っ込まれたので、それを踏まえてついでに引っかかった部分も混ぜて書こうと思います。
バージョンは1.7.10
ちなみにコードは[こ↑こ↓](https://github.com/C6H2Cl2/ReinforcedTools)からコピペです。
これで何が書いてあるかわかる人はこんな記事読んでないでGitHubの生コード読んで下さい。

#サンプルコード

```kotlin:MiningHammer.kt
class MiningHammer(material:Item.ToolMaterial,name:String) :ItemTool(-material.damageVsEntity+1,material,null),IReinforcedTools{
    init {
        unlocalizedName = "${name.toLowerCase()}MiningHammer"
        setTextureName("${ReinforcedToolsCore.Domain}:${name.toLowerCase()}MiningHammer")
        creativeTab = ReinforcedToolsRegistry.tabReinforcedTools
        maxDamage *= 6
        efficiencyOnProperMaterial *= 1.8f
        for (toolClass in getToolClasses(null)){
            setHarvestLevel(toolClass,material.harvestLevel+1)
        }
    }

    override fun getEnchanted(itemStack: ItemStack, enchantLevel: Int): ItemStack {
        val level = (enchantLevel.toFloat() * 1.2f).toInt()
        itemStack.addEnchantment(Enchantment.efficiency,level)
        itemStack.addEnchantment(Enchantment.fortune,level)
        itemStack.addEnchantment(Enchantment.unbreaking,level)
        return itemStack
    }

    override fun getToolType() = EnumToolType.MININGHAMMER

    override fun getToolClasses(stack: ItemStack?) = mutableSetOf("pickaxe","shovel","hammer","mininghammer","Pickaxe","Shovel","Hammer","MiningHammer","miningHammer")

    override fun func_150893_a(itemStack: ItemStack?, block: Block): Float {
        return if(itemStack != null && ReinforcedToolsCore.isToolEffective(itemStack,block,0)){
            efficiencyOnProperMaterial
        }else{
            1f
        }
    }

    override fun canHarvestBlock(block: Block, itemStack: ItemStack?): Boolean {
        return (block.getHarvestLevel(0) <= toolMaterial.harvestLevel && getToolClasses(itemStack).contains(block.getHarvestTool(0)))
    }

    override fun func_150897_b(block: Block): Boolean {
        return canHarvestBlock(block,null)
    }

    override fun getDigSpeed(itemStack: ItemStack?, block: Block, meta: Int): Float {
        return if(itemStack != null && ReinforcedToolsCore.isToolEffective(itemStack,block,0)){
            efficiencyOnProperMaterial
        }else{
            1f
        }
    }

}
```
#解説
まず、一行目

```kotlin
class MiningHammer(material:Item.ToolMaterial,name:String) :ItemTool(-material.damageVsEntity+1,material,null),IReinforcedTools
```
クラス宣言ですが、複合ツールを作る場合、ItemToolを継承します。
コンストラクタに、ダメージ補正(ToolMaterialのダメージ値にどれだけ加算するか),ToolMaterial,破壊可能ブロックのSetを入れます。
この第三引数ですが、バニラ用の部分ですので、modで追加するツールには何の関係もありません。
nullでも空のSetでも好きなものを突っ込んでやりましょう。
IReinforcedToolsはReinforcedToolsでの登録を簡単にするために作った独自インターフェースなので無視でおｋです。
IReinforcedToolsのオーバーライドは全部無視します。

次に、init句

```Kotlin
for (toolClass in getToolClasses(null)){
    setHarvestLevel(toolClass,material.harvestLevel+1)
}
```
この部分ですが、このsetHarvestLevelというものをすることで、ForgeHooks.isToolEffective()等を用いてブロックの破壊可能判定をするときに使ってくれます。
そして、ToolClassの設定ですが

```kotlin
override fun getToolClasses(stack: ItemStack?) = 
    mutableSetOf("pickaxe","shovel","hammer","mininghammer","Pickaxe","Shovel","Hammer","MiningHammer","miningHammer")
```
getToolClasses(ItemStack)をオーバーライドすることで設定できます。
Kotlinのお陰で、一行で済むので大変簡単です。
バニラは"pickaxe","axe","shovel"の3つです。
他のは一応入れてる程度なので後から他のmodで利用する予定がなければ上記の3つのうち必要なものだけを入れればいいでしょう。

```Kotlin
override fun func_150893_a(itemStack: ItemStack?, block: Block): Float {
    return if(itemStack != null && ReinforcedToolsCore.isToolEffective(itemStack,block,0)){
        efficiencyOnProperMaterial
    }else{
        1f
    }
}
```
この難読化がかかっている関数ですが、破壊速度を取得する関数です。
適正速度ならefficiencyOnProperMaterialを、そうでないのなら1fを返しましょう。
ちなみにこれを0fにすると全く破壊できません。

ところで、先程、破壊可能判定に使う関数としてForgeHooks.isToolEffective()を挙げました。
ところが、ここではReinforcedToolsCpre.isToolEffective()という独自の関数を使っています。
この関数を見てみましょう。

```Kotlin
companion object{
//省略
    fun isToolEffective(stack: ItemStack,block:Block,metadata:Int):Boolean{
       return stack.item.getToolClasses(stack).any { ("pickaxe" == it && (block === Blocks.redstone_ore || block === Blocks.lit_redstone_ore || block === Blocks.obsidian) )||block.isToolEffective(it, metadata) }
    }
}
```
これが一体何をやっているかですが、**ForgeHooks.isToolEffective()をKotlinに変換して、ForgeHooks.isToolEffectiveでの不具合への対処をしています**。
ForgeHooks.isToolEffective()をKotlinにすると以下のようになります

```Kotlin
companion object{
//省略
    fun isToolEffective(stack: ItemStack,block:Block,metadata:Int):Boolean{
       return stack.item.getToolClasses(stack).any {block.isToolEffective(it, metadata) }
    }
}
```
itemのToolClassesのいずれかがblockの適正だった場合、trueを返し、いずれも適正でなかったらfalseを返すというコードです。
これがどうしてダメなのかというと、**なぜかblock.isToolEffective()に、対象ブロックがレッドストーン鉱石,光っているレッドストーン鉱石,黒曜石の場合ToolClassがpickaxeでもfalseを返す**という謎な仕様が存在するためです。
一体どうしてこのようなコードが生まれたのかは私には全く理解ができないのですが、そのせいでこのように対処療法としてその3つに対しては自分で判定を書くしか無いわけです。
ここが私が複合ツールを作る上で、一番躓いたところです。
のこりはサラッと流しましょう。それほど難しいものはありません。

```Kotlin
override fun canHarvestBlock(block: Block, itemStack: ItemStack?): Boolean {
        return (block.getHarvestLevel(0) <= toolMaterial.harvestLevel &&
            getToolClasses(itemStack).contains(block.getHarvestTool(0)))
}
```
まぁ簡単に言うと、isToolEffective()に採掘レベルも合わせて判定しています。
ここではisToolEffective()を使っていませんが、isToolEffective()を使った場合とどちらが楽かは・・・
人の好みじゃないですかね

```Kotlin
override fun func_150897_b(block: Block): Boolean {
    return canHarvestBlock(block,null)
}
```
バニラ版canHarvestBlock()

```Kotlin
override fun getDigSpeed(itemStack: ItemStack?, block: Block, meta: Int): Float {
    return if(itemStack != null && ReinforcedToolsCore.isToolEffective(itemStack,block,0)){
     efficiencyOnProperMaterial
    }else{
        1f
    }
}
```
func_150893_a()と同じ
たしかどっちかがForgeでどっちかがバニラだったはずです。
多分どっちかだけでも行けるんでしょうが、一応両方使ってます。

#最後に
複合ツール、便利ですよね。
結構簡単に作れますが、Forge/バニラの仕様の壁があるので中級者向けといったところでしょうか。
ところで、結月ゆかりの誕生日である12/22にKotlinの1.1-M04が発表されたんですよね。
現地時間では12/21ですが。
日本時間では12/22でした。
やはり結月ゆかり×Kotlinをもっと推し進めよということなのでしょうか？