Minecraft 1.13.2のmodでGUIを表示してみた

#前文
実際に解説に入る前に、なぜこの記事を書いたのかについて書いておく。

##誰得？
既に1.12系以前のGUIに関する知識を有していて、1.13系に移行したい人向け。

##なぜGUI？
実際に1.12.2から1.13.2に移行した際、GUI関連、特に`registerExtensionPoint<T>`に関して適当な記事がまだなかったので。
「`NetworkHooks.openGui`を使え」という記事はたくさんあるのに。。。

#解説

##GUIファクトリの登録

前文で触れた`NetworkHooks.openGui`でGUIを表示するためには、事前に`ModLoadingContext#registerExtensionPoint<T>`を呼び出してGUIファクトリを登録しておく必要があります。

```ExampleMod.java
public ExampleMod() {
    /* 省略 */
    IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
    bus.addListener(this::doClientStuff);
    /* 省略 */
}

private void doClientStuff(final FMLClientSetupEvent event) {
    registerGuiFactory();
}

@OnlyIn(Dist.CLIENT)
private static void registerGuiFactory() {
    ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.GUIFACTORY, () -> (openContainer) -> {
        switch (openContainer.getId().toString()) {
            case "examplemod:foo":
                return new GuiContainerFoo(new ContainerFoo());
            case "examplemod:bar":
                return new GuiContainerBar(new ContainerBar());
            /* 必要に応じてGuiScreenの派生オブジェクトを返せるようにしておく */
        }
        return null;
    });
}
```

`ModLoadingContext#registerExtensionPoint<T>`は`ExtensionPoint<T>`と`Supplier<T>`を引数に取る。
第1引数に指定する`ExtensionPoint.GUIFACTORY`の型は`ExtensionPoint<Function<FMLPlayMessages.OpenContainer, GuiScreen>>`なので、第2引数には`Supplier<Function<FMLPlayMessages.OpenContainer, GuiScreen>>`型のオブジェクトを渡す必要がある。
実際には、`FMLPlayMessages.OpenContainer`を受け取って`GuiScreen`を継承したクラスのオブジェクトを返す`Function`を返す`Supplier`を渡す。ここで、`FMLPlayMessages.OpenContainer`のread系のメソッドで、GUI呼び出し側の`PacketBuffer`に書き込んだデータを受け取ることができる。
`GuiScreen`を継承したクラスについては1.12系と変わらないので割愛する。

また、SSPではなくSMPを対象にしたMOD制作では`registerExtensionPoint<T>`の呼び出し箇所に注意が必要となる。
サーバ側でも呼ばれる箇所（例えばコンストラクタ等）で`registerExtensionPoint<T>`を呼ぶと、「`GuiScreen`がないよ」とお叱りを受けてクラッシュするので、クライアント側でのみ呼び出される`FMLClientSetupEvent`を受け取るイベントハンドラ（前述の例では`doClientStuff`がこれに相当する）で呼び出すのがよい。


##GUIの呼び出し
###アイテムを右クリックした場合
`NetworkHooks.openGui`を呼び出してGUIを表示する。
第2引数のインタラクションオブジェクト（後述する）で、GUIの表すコンテンツのコンテナオブジェクトを作成したり、どのGUIを表示するかを指定したりする。
第3引数の`Consumer<PacketBuffer>`内で、GUIファクトリとの間でデータの受け渡しを行う。
この`PacketBuffer`のwrite系のメソッドで書き込んだデータは、GUIファクトリ側では`FMLPlayMessages.OpenContainer`のread系のメソッドで読み取ることができる。

```ItemFoo.java
public class ItemFoo extends Item {
    /* 省略 */
    public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
        ItemStack foo = player.getHeldItem(hand);
        if (!world.isRemote && player instanceof EntityPlayerMP) {
            NetworkHooks.openGui((EntityPlayerMP)player, new InteractionObjectFoo(foo, hand), (buffer) -> {
                /* bufferを通じて、必要な情報をGUIファクトリに受け渡す */
            });
        }
        return ActionResult.newResult(EnumActionResult.SUCCESS, foo);
    }
}
```

###ブロックを右クリックした場合
こちらも同様に`NetworkHooks.openGui`を呼び出してGUIを表示する。
ただ、第2引数のインタラクションオブジェクトは個別に実装する必要はなく、`TileEntity`の派生クラスに`IInteractionObject`インタフェースを実装することで対応する。

```BlockFoo.java
public class BlockFoo extends Block {
    /* 省略 */
    @Override 
    public boolean onBlockActivated(IBlockState state, World world, BlockPos pos, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
        if (world.isRemote) {
            return true;
        }
        TileEntity tile = world.getTileEntity(pos);
        if (tile instanceof TileEntityFoo && player instanceof EntityPlayerMP) {
            NetworkHooks.openGui((EntityPlayerMP)player, (TileEntityFoo)tile, (buffer) -> {
                /* bufferを通じて、必要な情報をGUIファクトリに受け渡す */
            });
        }
        return true;
    }
}
```

##インタラクションオブジェクト
`NetworkHooks.openGui`の第2引数に渡す`IInteractionObject`インタフェースを実装するオブジェクトについて解説する。

```InteractionObjectFoo.java
public class InteractionObjectFoo implements IInteractionObject {
    private final ItemStack foo;
    private final EnumHand hand;

    public InteractionObjectFoo(ItemStack foo, EnumHand hand) {
        this.foo = foo;
        this.hand = hand;
    }

    @Override
    public ITextComponent getCustomName() {
        return null;
    }

    @Override
    public ITextComponent getName() {
        return null;
    }

    @Override
    public boolean hasCustomName() {
        return false;
    }

    @Override
    public Container createContainer(InventoryPlayer arg0, EntityPlayer arg1) {
        return new ContainerFoo(this.foo, arg0, this.hand);
    }

    @Override
    public String getGuiID() {
        return "examplemod:foo";
    }
}
```

`createContainer`はクライアント・サーバの両側で用いられる`Container`クラスを継承したオブジェクトを返す。
`getGuiID`の返す文字列が、GUIファクトリ側で`FMLPlayMessages.OpenContainer.getId`として参照できる文字列となる。

##コンテナオブジェクト
コンテナオブジェクトについては1.12系から特段変わった点は少ない。
変更点といえば`addSlotToContainer`が`addSlot`に名称変更されたくらい。

```ContainerFoo.java
public class ContainerFoo extends Container {
    public ContainerFoo(ItemStack foo, InventoryPlayer inventoryPlayer, EnumHand hand) {
        addSlot(new Slot(/* 省略 */));
    }
}
```

#まとめ
既に1.14系のForgeも出ているので1.13系の記事は今更感がありますが、何かの役に立てればと思います。