Minecraft Modding 応用② 1.14.4【CompoundNBT】

#はじめに
[前回](https://qiita.com/RiSE4/items/e1a2ca7908a814fe5e82)の応用ではItem.Propertiesの解説を行いました。
今回は前回作った複合ツールに、モード切替の機能をつけていきます。

##作る前に...
1.14.4では、シャベルとクワの機能で、右クリックをすると耕地・草の道を作れます。
...で、この動作が見事に競合します。いろいろ考えたのですが、前回の記事で
**クワは武器としてアホみたいなスペックで用意したい派なので、クワの機能は追加しません。**
とがっつり書いていたのと、今回のメインはCompoundNBTを使用したモード切替なので、
右クリックの挙動うんぬんは実装せず、また別の記事で紹介しようと思います。悪しからず。

#開発環境 
- Windows 10
- Minecraft 1.14.4
- Minecraft Forge 1.14.4-28.1.79
- IntelliJ IDEA 2019.2.4
- Gradle 4.9
- Forge Gradle 3.+

#作っていきましょう！
コードを書いていく前に今回使うもので名称が変わったもの達の紹介です。
>**CompoundNBT**
これまでの**NBTTagCompound**クラスです。前回記事を書いた時はまだ同じ名称だと思ってました。すみません！！
メソッドの名前などはちょくちょく変わっていますが、でかい変更はなさそうです。

>**OnlyIn**, **Dist**
OnlyInはこれまでの**SideOnly**です。Distは**Side**です。
頑張ればClientProxy的なやつ(名前忘れた)を使わなくても
このアノテーションがあればクライアント側だけの処理を作ったりできます。おすすめはしません。

>**inventoryTick(...)**
これまでの**onUpdate**メソッドです。アイテムがワールドに存在する限りチック毎に呼ばれます。

>**getDestroySpeed(...)**
これまでの**getDigSpeed**メソッドです。1.7.10ではForgeがによって追加された
同じ名前のメソッドもありましたが、バニラのものはこの名称にかわりました。

>**StringTextComponent**
これまでの**TextComponentString**です。名前が変わっただけです。

その他の変更点では、メソッドの引数が変わったりしていますが、
大体は元のクラスをオーバーライドするので、Itemクラスを参照してください。
それではコードを書いていきます。今回で主目標3とついでの目標をクリアしちゃいたいと思います！

##クラスを書いていく
機能を箇条書すると、

- しゃがみながら右クリックするとモードを切り替える。
- チャットとツールチップにモードの名前を表示する。
- モード毎に破壊速度が変わる。
といった感じです。

今回はコードが複雑なので、解説はコードに直接書かせてもらいます。

<details><summary>ばり長いので格納</summary><div>

```java:ItemHammer.java
package rise.sunrisecraft.item;

import rise.sunrisecraft.common.ItemRegistry;
import rise.sunrisecraft.common.SunriseCraft;
import rise.sunrisecraft.lib.ItemProperties;
import rise.sunrisecraft.lib.ToolMaterial;

public class ItemHammer extends ToolItem {

    //前回の記事ではここまで書きました。
    public ItemHammer(String name, ToolMaterial material)
    {
        super(material.getAttackDamage(), 9F, material, new HashSet<>(),
                new ItemProperties()
                .setItemGroup(SunriseCraft.GROUP_SUNRISE)
                .setHarvestLevel(ToolType.PICKAXE, material.getHarvestLevel())
                .setHarvestLevel(ToolType.SHOVEL, material.getHarvestLevel())
                .setHarvestLevel(ToolType.AXE, material.getHarvestLevel())
        );
        this.setRegistryName(new ResourceLocation(SunriseCraft.MOD_ID, name));
        ItemRegistry.ITEMS.add(this);
    }

    /* 1.7.10: onUpdate(Same args) */
    public void inventoryTick(ItemStack itemStack, World world, Entity entity, int slot, boolean select)
    {
        //20秒毎に、アイテムがダメージを受けている場合、そのダメージを1回復する
        //ついでの目標達成！
        if(entity.ticksExisted % 400 == 0 && itemStack.isDamaged())
        {
            itemStack.setDamage(itemStack.getDamage() - 1);
        }
    }

    /* 1.7.10: getDigSpeed() ==> func_150893_a() */
    public float getDestroySpeed(ItemStack itemStack, BlockState state)
    {
        //ブロックが岩盤以外ならば、getEfficiency()を返す。
        return state.getBlock() != Blocks.BEDROCK ? this.getEfficiency(itemStack) : 1F;
    }

    public boolean canHarvestBlock(BlockState state)
    {
        //岩盤にはこのツールが使えないようにする。たぶん書かなくても問題ない。一応書いてる。
        return state.getBlock() != Blocks.BEDROCK;
    }

    //1.7.10ではbooleanを返したがActionResultを返すように(たぶん)1.12より変更された。
    public ActionResult<ItemStack> onItemRightClick(World world, PlayerEntity player, Hand hand)
    {
        ItemStack itemStack = player.getHeldItem(hand);

        //プレイヤーがしゃがんでいる場合
        if(player.isSneaking())
        {
            if(!world.isRemote)
            {
                //toggleMode()を呼び出す。
                toggleMode(itemStack);
                //モード変更がわかるように変更時にチャットに表示する。
                /* 1.12.2: TextComponentString */
                player.sendMessage(new StringTextComponent("Switch mode. Current Mode: " + this.getModeName(itemStack)));
            }

            //SUCCESSのフラグを返す。
            /* 1.12.2: EnumActionResult.SUCCESS */
            return new ActionResult<>(ActionResultType.SUCCESS, itemStack);
        }

        //処理が特になかったときはPASSのフラグを返す。
        return new ActionResult<>(ActionResultType.PASS, itemStack);
    }

    //モードをトグルさせる
    private void toggleMode(ItemStack itemStack)
    {
        //CompoundNBTがない場合新しく作成する。
        if(itemStack.getTag() == null)
            itemStack.setTag(new CompoundNBT());

        //取得したNBTから"mode"を取得、その数値を1増やし、2なら0にする。
        itemStack.getTag().putInt("mode", this.getMode(itemStack) < 2 ? this.getMode(itemStack) + 1 : 0);
    }

    //モードのintを返す。
    private int getMode(ItemStack itemStack)
    {
        //NBTがない場合0を返す。
        if(itemStack.getTag() == null)
            return 0;

        //NBTより"mode"を取得する。
        return itemStack.getTag().getInt("mode");
    }
     
    //モード毎の破壊速度を返す。
    private int getEfficiency(ItemStack itemStack)
    { 
        //モードを取得し、0~2に応じて好きな数値を返す。
        switch(this.getMode(itemStack))
        {
            case 0: return 20;
            case 1 : return 8;
            case 2 : return 128;
        }
        
        //なんもない場合は0を返す。
        return 0;
    }

    //モードの名前を返す。
    private String getModeName(ItemStack itemStack)
    {
        //モードのintに応じて名前をスイッチ。
        switch(this.getMode(itemStack))
        {
            case 0 : return "Normal";
            case 1 : return "Slow";
            case 2 : return "Fast";
        }

        return null;
    }

    /* 1.7.10: SideOnly(Side.CLIENT) */
    @OnlyIn(Dist.CLIENT)
    /* 1.7.10: List<(Unassigned: "String")> */
    public void addInformation(ItemStack itemStack, @Nullable World world, List<ITextComponent> list, ITooltipFlag flag)
    {
        //現在のモードがわかるようにツールチップに表示する。
        /* 1.7.10: StatCollector.translateToLocal(msg...) */
        list.add(new StringTextComponent("Current Mode: " + this.getModeName(itemStack)));
    }
}
```
</div>

こんな感じになります。モードの取得にintを使っていますが、shortやStringでも取得できます。
それに応じて細かいところを変えてあげればうまくいくと思います。

#終わりに
今回で複合ツールは完成です！
個人的にブロック破壊がメインのツールなのでその他の機能については別の形でまた何かつくろうと思います。

それでは！