レシピの追加(1.12.2)

概要

レシピを追加する。

前のチュートリアルまでと同様の部分は割愛する。

 

ソースコード

残念!ソースコードの実装などいらないのだよ!!

あくまでも前のチュートリアルまで実装できていればだが、レシピの実装はjson側で行うため追加コードは必要ない。

アセット

aluminium.json

{
  "type": "minecraft:crafting_shapeless",
  "ingredients": [
    {
      "item": "aluminiummod:aluminium_block"
    }
  ],
  "result": {
    "item": "aluminiummod:aluminium",
    "count": 9
  }
}

 

aluminium_block.json

{
  "type": "minecraft:crafting_shaped",
  "pattern": [
    "###",
    "###",
    "###"
  ],
  "key": {
    "#": {
      "item": "aluminiummod:aluminium"
    }
  },
  "result": {
    "item": "aluminiummod:aluminium_block"
  }
}

 

解説

MC1.12より、今までのレシピの登録方法が非推奨となり、代わりにjson形式でレシピを登録することになった。
その為、バニラにレシピを追加するだけのMODなら 最悪@Modをつけたクラスを作成するだけでjavaに触れるのは終わる。

 

名称はそのままforge側に登録され、/recipeコマンドで使われるため作られるアイテムのレジスター名にするのが最適である。
まず、type要素でクラフトタイプを指定する。"minecraft:crafting_shaped"で定形レシピ、minecraft:crafting_shapelessで不定形レシピを指定する。
次に、定形レシピではpatternでレシピの形を指定し、その文字に当てはまるアイテムをkeyで指定する。
不定形レシピではingredientsで指定する。
最後に、resultで結果を登録する。
それぞれ : で区切ってある前がMapのkey、後ろがvalueであると考えれば良い。
item要素で<modid>:<レジスター名>、count要素で個数、data要素でメタデータ(アイテムなのでblockstateは登録できない)を指定する。
アイテムの種類を増やしたいときはkey若しくはingredientsを下のように変更すれば良い。
定形・不定形レシピ
  • 定形
    "key": {
        "#": {
          "item": "aluminiummod:aluminium"
        },
        "A": {
          "item": "minecraft:skull",
          "data": 4
      },
  • 不定形
    "ingredients": [
        {
          "item": "aluminiummod:aluminium_block"
        },
        {
          "item": "minecraft:skull",
          "data": 4
      ],

 

レシピ追加通知

MC1.12からの新機能として、「レシピブック」がある。
また、バニラのアイテムは入手したとき右上に「新規レシピ追加」という通知が出る。
今回はアイテムを大量に追加するMOD用に「レシピブック」とイベントを使って通知を出す。
(進捗のシステムを使ってレシピを出すことも出来るが、書くべきjsonファイル数が多くなる。そのやり方は進捗の追加チュートリアルで記載予定。)
追加チュートリアル

AluminiumMod.java

package com.tntmodders.tutorial;

import net.minecraft.block.Block;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import java.util.List;

@Mod(modid = "aluminiummod", version = "1.0", name = "AluminiumMod")
public class AluminiumMod {
    public static final Item ALUMINIUM = new ItemAluminium();
    public static final Block ALUMINIUM_BLOCK = new BlockAluminium();
    private static final AluminiumRecipeHolder HOLDER = new AluminiumRecipeHolder();

    @Mod.Instance("aluminiummod")
    public static AluminiumMod aluminiumInstance;

    @Mod.EventHandler
    //この関数でMODファイル自体をイベントの発火先にする。
    public void construct(FMLConstructionEvent event) {
        MinecraftForge.EVENT_BUS.register(this);
    }

    //アイテムを登録するイベント。 旧preinitのタイミングで発火する。
    @SubscribeEvent
    public void registerItems(RegistryEvent.Register event) {
        event.getRegistry().register(ALUMINIUM);
        event.getRegistry().register(new ItemBlock(ALUMINIUM_BLOCK).setRegistryName("aluminiummod", "aluminium_block"));
    }

    //ブロックを登録するイベント。 旧preinitのタイミングで発火する。
    @SubscribeEvent
    public void registerBlocks(RegistryEvent.Register event) {
        event.getRegistry().register(ALUMINIUM_BLOCK);
    }

    //モデルを登録するイベント。SideOnlyによってクライアント側のみ呼ばれる。旧preinitのタイミングで発火する。
    @SubscribeEvent
    @SideOnly(Side.CLIENT)
    public void registerModels(ModelRegistryEvent event) {
        ModelLoader.setCustomModelResourceLocation(ALUMINIUM, 0, new ModelResourceLocation(new ResourceLocation("aluminiummod", "aluminium"), "inventory"));
        ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(ALUMINIUM_BLOCK), 0, new ModelResourceLocation(new ResourceLocation("aluminiummod", "aluminium_block"), "inventory"));
    }

    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        HOLDER.register();
    }

    //アイテムを拾ったときのイベント。
    @SubscribeEvent
    public void onPickupItem(EntityItemPickupEvent event) {
        this.aluminiumUnlockRecipes(event.getItem().getItem(), event.getEntityPlayer());
    }

    private void aluminiumUnlockRecipes(ItemStack stack, EntityPlayer player) {
        if (FMLCommonHandler.instance().getSide().isClient()) {
            Item item = stack.getItem();
            int meta = stack.getMetadata();
            ItemStack itemStack = new ItemStack(item, 1, meta);
            //もしレシピを保持するリストに合致すれば
            if (!AluminiumRecipeHolder.map.isEmpty() && AluminiumRecipeHolder.map.containsKey(itemStack)) {
                List list = AluminiumRecipeHolder.map.get(itemStack);
                //player.unlockRecipes(ResourceLocation[] locations)でレシピブックに追加する。
                player.unlockRecipes(list.toArray(new ResourceLocation[list.size()]));
            }
        }
    }

    //コンテナを閉じたとき(チェストやプレイヤーインベントリなど)のイベント。
    @SubscribeEvent
    public void onCloseContainer(PlayerContainerEvent.Close event) {
        for (ItemStack itemStack : event.getEntityPlayer().inventoryContainer.getInventory()) {
            this.aluminiumUnlockRecipes(itemStack, event.getEntityPlayer());
        }
    }
}

 

AluminiumRecipeHolder.java

package com.tntmodders.tutorial;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.FMLCommonHandler;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class AluminiumRecipeHolder {
    //ItemStackよりそのアイテムが鍵となるレシピを取得できるようにする。
    public static final Map> map = new ItemStackHashMap();

    public void register() {
        if (FMLCommonHandler.instance().getSide().isClient()) {
            //assets//recipes/よりリソースを取得する。
            this.getResource("assets/aluminiummod/recipes/");
        }
    }

    public void getResource(String path) {
        ClassLoader loader = AluminiumMod.class.getClassLoader();
        URL url = loader.getResource(path);
        //jarファイル内か否かで処理が変化する。
        if (url.getProtocol().equals("jar")) {
            String[] strings = url.getPath().split(":");
            String leadPath = strings[strings.length - 1].split("!")[0];
            File f = new File(leadPath);
            JarFile jarFile;
            try {
                //jarファイル自体を取得する。(zipファイル・jarファイルとして扱う事ができる。)
                jarFile = new JarFile(f);
                Enumeration enumeration = jarFile.entries();
                while (enumeration.hasMoreElements()) {
                    JarEntry entry = enumeration.nextElement();
                    String s = entry.getName();
                    if (s != null && s.startsWith(path) && s.endsWith(".json")) {
                        InputStream stream;
                        try {
                            stream = loader.getResourceAsStream(s);
                            //inputstreamを使ってjarファイル内のjsonを読み込む。
                            this.readStream(stream, s);
                            stream.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            List list = this.getListFile(path);
            if (list.size() > 0) {
                for (File recipe : list) {
                    InputStream stream;
                    try {
                        stream = new FileInputStream(recipe);
                        this.readStream(stream, recipe.getName());
                        stream.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void readStream(InputStream stream, String name) {
        //inputstreamよりJSONを読み込む。
        JsonReader reader = new JsonReader(new InputStreamReader(stream));
        JsonObject jsonObject = new Gson().fromJson(reader, JsonObject.class);
        //文字列の中にパスが紛れ込んだ場合それを消す。また、".json"を抜いてResourceLocationとして保存する。
        ResourceLocation location = new ResourceLocation("aluminiummod", name.replaceAll("assets/aluminiummod/recipes/", "")
                .replaceAll(".json", ""));

        //定形レシピでキーを"#"にしたアイテムを鍵とする。
        if (jsonObject.has("key") && jsonObject.getAsJsonObject("key").has("#")) {
            Item item = Item.getByNameOrId(jsonObject.getAsJsonObject("key").getAsJsonObject("#").get("item").getAsString());
            int i = 0;
            if (jsonObject.getAsJsonObject("key").getAsJsonObject("#").has("data")) {
                i = jsonObject.getAsJsonObject("key").getAsJsonObject("#").get("data").getAsInt();
            }
            ItemStack stack = new ItemStack(item, 1, i);
            List locations = map.containsKey(stack) ? map.get(stack) : new ArrayList<>();
            locations.add(location);
            map.put(stack, locations);
        }
        //不定形レシピで一番上に書いたアイテムを鍵とする。
        else if (jsonObject.has("ingredients") && jsonObject.getAsJsonArray("ingredients").get(0).getAsJsonObject().has("item")) {
            String s = jsonObject.getAsJsonArray("ingredients").get(0).getAsJsonObject().get("item").getAsString();
            Item item = Item.getByNameOrId(s);
            int i = 0;
            if (jsonObject.getAsJsonArray("ingredients").get(0).getAsJsonObject().has("data")) {
                i = jsonObject.getAsJsonArray("ingredients").get(0).getAsJsonObject().get("data").getAsInt();
            }
            ItemStack stack = new ItemStack(item, 1, i);
            List locations = map.containsKey(stack) ? map.get(stack) : new ArrayList<>();
            locations.add(location);
            map.put(stack, locations);
        }
    }

    //ファイルを全て取得する。これもjarか否かによって変わる。
    private List getListFile(String path) {
        List files = new ArrayList<>();
        ClassLoader loader = AluminiumMod.class.getClassLoader();
        URL url = loader.getResource(path);
        if (url.getProtocol().equals("jar")) {
            String[] strings = url.getPath().split(":");
            String leadPath = strings[strings.length - 1].split("!")[0];
            File f = new File(leadPath);
            JarFile jarFile;
            try {
                jarFile = new JarFile(f);
                Enumeration enumeration = jarFile.entries();
                while (enumeration.hasMoreElements()) {
                    JarEntry entry = enumeration.nextElement();
                    String s = entry.getName();
                    if (s != null && s.startsWith(path) && s.endsWith(".json")) {
                        files.add(new File(loader.getResource(s).getPath()));
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            File packFile = FMLCommonHandler.instance().findContainerFor(AluminiumMod.aluminiumInstance).getSource();
            File newFile = new File(packFile.toURI().getPath() + path);
            files = Arrays.asList(newFile.listFiles());
        }
        return files;
    }

    //ItemStackを使ったマップを定義する。
    public static class ItemStackHashMap> extends HashMap {

        @Override
        public V get(Object key) {
            if (key instanceof ItemStack && this.containsKey(key)) {
                for (Map.Entry entry : this.entrySet()) {
                    if (entry.getKey().getItem() == ((ItemStack) key).getItem() && entry.getKey().getMetadata() == ((ItemStack) key).getMetadata()) {
                        return entry.getValue();
                    }
                }
            }
            return null;
        }

        @Override
        public boolean containsKey(Object key) {
            if (key instanceof ItemStack) {
                ItemStack itemStack = (ItemStack) key;
                for (ItemStack stack : this.keySet()) {
                    if (stack.getItem() == itemStack.getItem() && stack.getMetadata() == itemStack.getMetadata()) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}