環境
minecraft 1.7.10
多分他のバージョンでも同じ。それこそ1.2.5とかでも
背景
例えばしたの2つの例を見てほしい。このように左右逆に並べた時に別々のアイテムを作るレシピを登録したいとする。
何も考えずに
1.7のレシピ追加 - Minecraft Modding Wiki
を真似して
package com.example.examplemod;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
@Mod(modid = ExampleMod.MODID, version = ExampleMod.VERSION)
public class ExampleMod
{
public static final String MODID = "examplemod";
public static final String VERSION = "1.0";
@EventHandler
public void init(FMLInitializationEvent event)
{
GameRegistry.addShapedRecipe(new ItemStack(Blocks.stone),
"WA",
'A',Blocks.dirt,
'W',Blocks.grass);
GameRegistry.addShapedRecipe(new ItemStack(Blocks.cobblestone),
"AW",
'A',Blocks.dirt,
'W',Blocks.grass);
}
}
のようにすると思う。このとき、予想に反して両方とも焼石が生成されてしまう。
原因
左右反転レシピが自動的に登録される仕様がMinecraftにはある。このよけいなおせっかい機能が原因である。
そもそもGameRegistry.addShapedRecipe
がどう実装されているかというと
https://gitlab.zveronline.ru/Mirrors/Minecraft/MinecraftForge/-/blob/1.7.10/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java#L248-251
public static IRecipe addShapedRecipe(ItemStack output, Object... params)
{
return CraftingManager.getInstance().addRecipe(output, params);
}
net.minecraft.item.crafting.CraftingManager
に丸投げされている。
これをデコンパイルして追跡した先人たち曰く、内部でいろいろ処理された後、net.minecraft.item.crafting.ShapedRecipes
のインスタンスが作られるのだそうだ。この中にmatches
というメソッドがある。でコンパイルした結果を読むと、matches
が呼び出しているcheckMatch
というprivate methodの第4引数にtrueが渡るときとfalseが渡るときがあり、先にtrueのときが処理されている。反転が先に判定される状態だ。これを書き換えることでなんとかできそうだ。
そこでShapedRecipes
をoverrideしてmatches
を書き換えることにする。ただ、checkMatch
がprivate methodで呼び出せないので仕方ないからデコンパイルしたものを持ってきた。
解決方法
デコンパイルしたコードを結構引っ張ってきたようなコードとなる
package com.example.examplemod;
import java.util.HashMap;
import net.minecraft.block.Block;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.ShapedRecipes;
import net.minecraft.world.World;
import cpw.mods.fml.common.registry.GameRegistry;
public class NonReversedShapedRecipe {
public static ShapedRecipes addShapedRecipe(ItemStack output, Object... params) {
String s = "";
int i = 0;
int j = 0;
int k = 0;
if (params[i] instanceof String[]) {
String[] astring = (String[]) ((String[]) params[i++]);
for (int l = 0; l < astring.length; ++l) {
String s1 = astring[l];
++k;
j = s1.length();
s = s + s1;
}
} else {
while (params[i] instanceof String) {
String s2 = (String) params[i++];
++k;
j = s2.length();
s = s + s2;
}
}
HashMap<Character, ItemStack> hashmap;
for (hashmap = new HashMap<>(); i < params.length; i += 2) {
Character character = (Character) params[i];
ItemStack itemstack1 = null;
if (params[i + 1] instanceof Item) {
itemstack1 = new ItemStack((Item) params[i + 1]);
} else if (params[i + 1] instanceof Block) {
itemstack1 = new ItemStack((Block) params[i + 1], 1, 32767);
} else if (params[i + 1] instanceof ItemStack) {
itemstack1 = (ItemStack) params[i + 1];
}
hashmap.put(character, itemstack1);
}
ItemStack[] aitemstack = new ItemStack[j * k];
for (int i1 = 0; i1 < j * k; ++i1) {
char c0 = s.charAt(i1);
if (hashmap.containsKey(Character.valueOf(c0))) {
aitemstack[i1] = ((ItemStack) hashmap.get(Character.valueOf(c0))).copy();
} else {
aitemstack[i1] = null;
}
}
ShapedRecipes shapedrecipes = new ShapedRecipes(j, k, aitemstack, output) {
@Override
public boolean matches(InventoryCrafting inv, World worldIn) {
for (int i = 0; i <= 3 - this.recipeWidth; ++i) {
for (int j = 0; j <= 3 - this.recipeHeight; ++j) {
// When you uncomment this, you can get vanilla's behavior.
// if (this.checkMatch(inv, i, j, true)) {
// return true;
// }
if (this.checkMatch(inv, i, j, false)) {
return true;
}
}
}
return false;
}
private boolean checkMatch(InventoryCrafting inv, int i, int j, boolean p_77573_4_) {
for (int k = 0; k < 3; ++k) {
for (int l = 0; l < 3; ++l) {
int i1 = k - i;
int j1 = l - j;
ItemStack itemstack = null;
if (i1 >= 0 && j1 >= 0 && i1 < this.recipeWidth && j1 < this.recipeHeight) {
if (p_77573_4_) {
itemstack = this.recipeItems[this.recipeWidth - i1 - 1 + j1 * this.recipeWidth];
} else {
itemstack = this.recipeItems[i1 + j1 * this.recipeWidth];
}
}
ItemStack itemstack1 = inv.getStackInRowAndColumn(k, l);
if (itemstack1 != null || itemstack != null) {
if (itemstack1 == null && itemstack != null || itemstack1 != null && itemstack == null) {
return false;
}
if (itemstack.getItem() != itemstack1.getItem()) {
return false;
}
if (itemstack.getItemDamage() != 32767
&& itemstack.getItemDamage() != itemstack1.getItemDamage()) {
return false;
}
}
}
}
return true;
}
};
GameRegistry.addRecipe(shapedrecipes);
return shapedrecipes;
}
}
なんとこの記事を書いている今日、はじめてまともにJavaに触ったのだが、ShapedRecipes shapedrecipes = new ShapedRecipes(j, k, aitemstack, output) {
以下の部分は匿名(無名)クラスという機能らしい。ここでは無名のShapedRecipesを継承したクラス作ってることになる。
あとはGameRegistry.addShapedRecipe
をNonReversedShapedRecipe.addShapedRecipe
に置き換えるだけでよい。
ちゃんと左右を入れ替えたときに別のアイテムを出すことができた
謝辞
この記事は解析を担当した某Discord鯖のE氏を始めとする方々の成果を私が記事としてまとめたものです。サンプルコードが動くことは自力で再現していますがそれ以上のことは私は行っていません。解析に感謝します。