Minecraftで乗り物を瞬時に呼び出すMODをつくった

# はじめに
こんにちは。最近は寝ても覚めてもMinecraftのことばかり考えているinokinnです。

Minecraft、楽しいですよね。
ゲーム内でものづくりするのも楽しいですが、近頃はAWS上にマルチプレイ用のサーバを構築して身内でワイワイ楽しみながら運用したり、自分のアバターのスキンを自作して唯一無二のキャラクターを生み出したり、色んな方が作成されたゲームの拡張プログラム(MOD)を導入してカオスな世界を楽しんだりと色んな方面からクラフトを満喫しています。

さて、そんなMinecraftですが、このゲームではペットを飼うことも出来ます。
馬を飼って、それに乗ることもできるのですが、離れたところに置いてきたりすると、見つけ出すのが困難になってしまいます。

そこで、「愛馬を瞬時に呼び寄せることができればなぁ…」と思ったのですが、私が探したところそういったMODは見つからなかったため、無ければ作ってみるかと思い立ち、表題のMODを作ったので、以下にこれまでの作業工程をまとめました。

# MOD要件
- 乗り物MOB(以下、マウント)を登録し、アイテム使用時に即座に登録したマウントを呼び出すアイテム「マウントホイッスル」を追加する
- 愛馬が死んだら悲しいので、<b>登録したマウントを不死身にする</b>

# やったこと
## MOD開発環境を整える
これについてはググれば手順は出てくるので割愛します。

## アイテムを追加する
新規にアイテムを追加する際の手順は以下のとおりです。
### MOD本体プログラムに、アイテムの登録処理を書く

以下に実際のコードを示します。(import文は省略)

```java:MountWhistle.java
@Mod(modid = MountWhistle.MODID, name = MountWhistle.NAME, version = MountWhistle.VERSION)
public class MountWhistle {
    public static final String MODID = "mountwhistle";
    public static final String NAME = "Mount Whistle";
    public static final String VERSION = "1.0.2";
    public static final String PAKETNAME = "MountWhistle";

    public static Item mountWhistle;

    @Mod.EventHandler
    public void construct(FMLConstructionEvent event) {
        MinecraftForge.EVENT_BUS.register(this);
    }

    @SubscribeEvent
    public void registerItems(RegistryEvent.Register<Item> event) {
    	mountWhistle = new Whistle()
    			.setCreativeTab(CreativeTabs.TRANSPORTATION)
    			.setUnlocalizedName("mountwhistle")
    			.setRegistryName("mountwhistle")
    			.setMaxStackSize(1);
        event.getRegistry().register(mountWhistle);
    }

    @SubscribeEvent
    @SideOnly(Side.CLIENT)
    public void registerModels(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(mountWhistle, 0, new ModelResourceLocation(new ResourceLocation("mountwhistle", "mountwhistle"), "inventory"));
    }
}
```


- `registerItems(RegistryEvent.Register<Item> event)` メソッド内で、アイテムの登録処理を書きます。<br>アイテムの情報をここに記述します。
- `registerModels(ModelRegistryEvent event)` メソッド内で、アイテムのモデルの登録処理を書きます。これにより、 `src.main.resources.assets.MOD_ID.models.items` 下のjsonファイルを参照します。

### アイテムのテクスチャに関する情報をjsonファイルに記述

`src/main/resources/assets/MOD_ID/models/items` パッケージを作成し、その下に `アイテム名.json` で画像ファイルを指定するjsonファイルを書きます。

```json:mountwhistle.json
{
	"parent": "item/generated",
	"textures": {
		"layer0": "mountwhistle:items/whistle"
	}
}
```

### アイテムのテクスチャをプロジェクトに追加

`src/main/resources/assets/MOD_ID/textures/items` パッケージを作成し、その下に `アイテム名.png` で画像ファイルを保存します。

## アイテムの使用時の挙動を実装する

ここからが本番です。
まず、マウントホイッスルのアイテムのクラスを作成します。
Minecraftのアイテムの基底クラスは `net.minecraft.item.Item` なので、これをオーバーライドする形で実装していきます。

まず、実装したソースコードを以下に示し、それから解説に入ります。

### Whistle.javaのソースコード

```java:Whistle.java
public class Whistle extends Item {

	@Override
	public void setDamage(ItemStack stack, int damage) {
		return;
	}

	@Override
	public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand handIn) {
		Entity mount = playerIn.getRidingEntity();
		ItemStack itemStack = playerIn.getHeldItem(handIn);
		// 騎乗しているか
		if (mount != null) {
			// 現在騎乗しているマウントを登録
			NBTTagCompound nbtTag = itemStack.getTagCompound();
			if (nbtTag == null) {
				nbtTag = new NBTTagCompound();
				itemStack.setTagCompound(nbtTag);
			}

			if (nbtTag.hasKey("MountId") &&
					!UUID.fromString(nbtTag.getString("MountId")).equals(mount.getUniqueID())) {
				return new ActionResult<ItemStack>(EnumActionResult.PASS, playerIn.getHeldItem(handIn));
			}

			String name = mount.getName();

			if (worldIn.isRemote) {
				Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentString("マウントホイッスルに" + name + "を登録しました！"));
			} else {
				nbtTag.setString("MountId", mount.getUniqueID().toString());
				nbtTag.setString("Name", name);
				itemStack.setStackDisplayName(name);

				// マウントを不死身にする
				mount.setEntityInvulnerable(true);
			}
		} else {
			NBTTagCompound nbtTag = itemStack.getTagCompound();
			if (nbtTag == null || !nbtTag.hasKey("MountId")) {
				if (worldIn.isRemote) {
					Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentString("騎乗中に使用するとマウントを登録できます。"));
				}
				return new ActionResult<ItemStack>(EnumActionResult.PASS, playerIn.getHeldItem(handIn));
			}

			UUID mountId = UUID.fromString(nbtTag.getString("MountId"));

			MinecraftServer server =  worldIn.getMinecraftServer();

			if(server == null) {
				Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentString("召喚を試みました。"));
			} else {
				// すべてのエンティティからマウントを探す
				mount = server.getEntityFromUuid(mountId);
				mount.setLocationAndAngles(playerIn.posX, playerIn.posY, playerIn.posZ, 0.0F, 0.0F);

				if (worldIn.isRemote) {
					if (mount != null) {
						String name = mount.getName();
						Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentString(name + " を召喚しました。"));
					}
				}
			}
		}

		return new ActionResult<ItemStack>(EnumActionResult.PASS, playerIn.getHeldItem(handIn));
	}
}
```

### 解説

#### アイテムを消費しないようにする
通常、アイテムは使用したら無くなりますが、マウントホイッスルは何度でも使いたいアイテムです。
使った際の消費処理を行っているメソッドは `setDamage(ItemStack stack, int damage)` なので、これをオーバーライドし、即 `return` することで何も起こらないようにしています。

#### アイテムを使った際の挙動
アイテムを右クリックで使用した際に呼ばれるメソッドは `ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand handIn)` です。
これをオーバーライドすることで、右クリック時のイベントを実装することが出来ます。

ここでは、まず、プレイヤーがマウントに騎乗しているかどうかを判定します。
騎乗していれば、マウントホイッスルのNBTにマウントを登録し、マウントを不死身にする処理を行います。
騎乗していなければ、マウントホイッスルのNBTに登録されているマウントをプレイヤーの座標に召喚します。

#### NBT とは
アイテムに情報を付与する際、 `NBT` という仕組みを使います。
NBTというのは、`Named Binary Tag` の略で、MinecraftではNBTというデータ形式で様々なデータが保存されています。
アイテムにNBTタグが付いていなければ、 `NBTTagCompound` のインスタンスを作成して最初に `setTagCompound(nbtTag)`した後、マウントのUUID(エンティティオブジェクトのユニークな識別子)を登録します。
最初の`setTagCompound(nbtTag)`をやらないと、アイテムに情報が保持されない点にご注意ください。
アイテムにNBTタグを付与する際に注意することとして、NBTタグはアイテムには付与できず、実際に付与する対象は `ItemStack` であるという点です。
ItemStackとは、アイテムの束のことで「インベントリの中の1枠」のことを指します。

#### マルチプレイの際に意識すること
MODのプログラムは、クライアントとサーバの両方で発火します。
しかし、この処理はクライアントだけで動いてほしい、もしくはその逆のパターンがあると思います。
その際のハンドリングとして、 `World` オブジェクトの `isRemote` の値を見ることで分岐することが出来ます。
クライアント側なら`true`、サーバ側なら`false`です。
ここでは、クライアント側での実行時のみチャットメッセージを表示し、サーバ側での実行時のみ、マウントの座標変更を行っています。
サーバ・クライアントのハンドリングを行わないと、一瞬だけ自分の元にマウントが移動した後、サーバ側の座標からマウントは歩きだすため、瞬時に元の位置に戻ってしまいます。

# できたもの
GitHubにアップしておきました。
https://github.com/inokinn/MountWhistle


# 実際に動かしてみる

まずはマウントホイッスルを入手します。
デフォルトでは、 <a href="http://www.9minecraft.net/chococraft-mod/">Chococraft </a>というMODで追加されるアイテム「ギサールの野菜」9個でクラフトできるようになっています。
マウント騎乗時、マウントホイッスルを使用することで、アイテム名がマウントの名前になれば登録成功です。
あとは、非騎乗時にマウントホイッスルを使用し、マウントがプレイヤーの元にやってくればOKです。
※オンメモリのエンティティしか参照できないMinecraftの仕様上、あまりにも遠くのマウントを呼び出すことは出来ません。

# おわりに

MinecraftのMOD開発関連は、情報が少ないこと、ビルドして動作確認に時間がかかること、シングルプレイとマルチプレイで挙動が異なること等があり、トライアンドエラーを繰り返した結果、開発開始から完成まで2日ほどかかりました。
特に日本語のドキュメントは古い上にほとんどなく、実際のソースコード読むしかない感じでした。
日本国内でも、もっとMinecraftのMOD開発が盛り上がってほしいなー。