Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

初めに

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

サンプルコード

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
        }
    }

}

解説

まず、一行目

class MiningHammer(material:Item.ToolMaterial,name:String) :ItemTool(-material.damageVsEntity+1,material,null),IReinforcedTools

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

次に、init句

for (toolClass in getToolClasses(null)){
    setHarvestLevel(toolClass,material.harvestLevel+1)
}

この部分ですが、このsetHarvestLevelというものをすることで、ForgeHooks.isToolEffective()等を用いてブロックの破壊可能判定をするときに使ってくれます。
そして、ToolClassの設定ですが

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

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

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()という独自の関数を使っています。
この関数を見てみましょう。

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にすると以下のようになります

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つに対しては自分で判定を書くしか無いわけです。
ここが私が複合ツールを作る上で、一番躓いたところです。
のこりはサラッと流しましょう。それほど難しいものはありません。

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

まぁ簡単に言うと、isToolEffective()に採掘レベルも合わせて判定しています。
ここではisToolEffective()を使っていませんが、isToolEffective()を使った場合とどちらが楽かは・・・
人の好みじゃないですかね

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

バニラ版canHarvestBlock()

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をもっと推し進めよということなのでしょうか?

C6H2Cl2
MinecraftのMod製作を主にしているプログラマー。高校生。 最近Kotlin推しで、Kotlinで快適にmoddingをするためにいろいろやっている。 Kotlinはいいぞ。 結月ゆかりが大好き。結月ゆかりはいいぞ。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした