2017-06-14 17:14:56 +00:00
|
|
|
/*
|
|
|
|
* Minecraft Forge
|
|
|
|
* Copyright (c) 2016.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation version 2.1
|
|
|
|
* of the License.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
package net.minecraftforge.common.crafting;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URISyntaxException;
|
|
|
|
import java.nio.file.FileSystem;
|
|
|
|
import java.nio.file.FileSystems;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.Set;
|
2017-06-27 22:18:52 +00:00
|
|
|
import java.util.function.BiFunction;
|
2017-06-14 17:14:56 +00:00
|
|
|
import java.util.function.BooleanSupplier;
|
2017-06-27 22:18:52 +00:00
|
|
|
import java.util.function.Function;
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
|
|
|
|
import org.apache.commons.io.FilenameUtils;
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
import org.apache.logging.log4j.Level;
|
|
|
|
|
|
|
|
import com.google.common.collect.Lists;
|
|
|
|
import com.google.common.collect.Maps;
|
|
|
|
import com.google.common.collect.Sets;
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
import com.google.gson.GsonBuilder;
|
|
|
|
import com.google.gson.JsonArray;
|
|
|
|
import com.google.gson.JsonElement;
|
|
|
|
import com.google.gson.JsonObject;
|
|
|
|
import com.google.gson.JsonParseException;
|
|
|
|
import com.google.gson.JsonSyntaxException;
|
|
|
|
|
|
|
|
import net.minecraft.block.Block;
|
|
|
|
import net.minecraft.item.Item;
|
|
|
|
import net.minecraft.item.ItemStack;
|
|
|
|
import net.minecraft.item.crafting.CraftingManager;
|
|
|
|
import net.minecraft.item.crafting.IRecipe;
|
|
|
|
import net.minecraft.item.crafting.Ingredient;
|
|
|
|
import net.minecraft.item.crafting.ShapedRecipes;
|
|
|
|
import net.minecraft.item.crafting.ShapelessRecipes;
|
|
|
|
import net.minecraft.nbt.JsonToNBT;
|
|
|
|
import net.minecraft.nbt.NBTException;
|
|
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
|
|
import net.minecraft.util.JsonUtils;
|
|
|
|
import net.minecraft.util.NonNullList;
|
|
|
|
import net.minecraft.util.ResourceLocation;
|
2017-06-24 22:34:09 +00:00
|
|
|
import net.minecraftforge.fml.common.FMLCommonHandler;
|
2017-06-14 17:14:56 +00:00
|
|
|
import net.minecraftforge.fml.common.FMLLog;
|
|
|
|
import net.minecraftforge.fml.common.Loader;
|
|
|
|
import net.minecraftforge.fml.common.ModContainer;
|
|
|
|
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
|
|
|
import net.minecraftforge.oredict.OreDictionary;
|
|
|
|
import net.minecraftforge.oredict.OreIngredient;
|
|
|
|
import net.minecraftforge.oredict.ShapedOreRecipe;
|
|
|
|
import net.minecraftforge.oredict.ShapelessOreRecipe;
|
2017-06-19 22:02:18 +00:00
|
|
|
import net.minecraftforge.registries.ForgeRegistry;
|
|
|
|
import net.minecraftforge.registries.GameData;
|
|
|
|
import net.minecraftforge.registries.RegistryManager;
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
public class CraftingHelper {
|
|
|
|
|
|
|
|
private static final boolean DEBUG_LOAD_MINECRAFT = false;
|
|
|
|
private static Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
|
|
|
private static Map<ResourceLocation, IConditionFactory> conditions = Maps.newHashMap();
|
|
|
|
private static Map<ResourceLocation, IIngredientFactory> ingredients = Maps.newHashMap();
|
|
|
|
private static Map<ResourceLocation, IRecipeFactory> recipes = Maps.newHashMap();
|
|
|
|
|
|
|
|
static {
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void register(ResourceLocation key, IConditionFactory factory)
|
|
|
|
{
|
|
|
|
if (conditions.containsKey(key))
|
|
|
|
throw new IllegalStateException("Duplicate recipe condition factory: " + key);
|
|
|
|
conditions.put(key, factory);
|
|
|
|
}
|
|
|
|
public static void register(ResourceLocation key, IRecipeFactory factory)
|
|
|
|
{
|
|
|
|
if (recipes.containsKey(key))
|
|
|
|
throw new IllegalStateException("Duplicate recipe factory: " + key);
|
|
|
|
recipes.put(key, factory);
|
|
|
|
}
|
|
|
|
public static void register(ResourceLocation key, IIngredientFactory factory)
|
|
|
|
{
|
|
|
|
if (ingredients.containsKey(key))
|
|
|
|
throw new IllegalStateException("Duplicate recipe ingredient factory: " + key);
|
|
|
|
ingredients.put(key, factory);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static Ingredient getIngredient(Object obj)
|
|
|
|
{
|
|
|
|
if (obj instanceof Ingredient)
|
|
|
|
return (Ingredient)obj;
|
|
|
|
else if (obj instanceof ItemStack)
|
2017-06-18 01:06:04 +00:00
|
|
|
return Ingredient.fromStacks(((ItemStack)obj).copy());
|
2017-06-14 17:14:56 +00:00
|
|
|
else if (obj instanceof Item)
|
2017-06-18 01:06:04 +00:00
|
|
|
return Ingredient.fromItem((Item)obj);
|
2017-06-14 17:14:56 +00:00
|
|
|
else if (obj instanceof Block)
|
2017-06-18 01:06:04 +00:00
|
|
|
return Ingredient.fromStacks(new ItemStack((Block)obj, 1, OreDictionary.WILDCARD_VALUE));
|
2017-06-14 17:14:56 +00:00
|
|
|
else if (obj instanceof String)
|
|
|
|
return new OreIngredient((String)obj);
|
|
|
|
else if (obj instanceof JsonElement)
|
|
|
|
throw new IllegalArgumentException("JsonObjects must use getIngredient(JsonObject, JsonContext)");
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
public static Ingredient getIngredient(JsonElement json, JsonContext context)
|
|
|
|
{
|
|
|
|
if (json == null || json.isJsonNull())
|
|
|
|
throw new JsonSyntaxException("Json cannot be null");
|
|
|
|
if (context == null)
|
|
|
|
throw new IllegalArgumentException("getIngredient Context cannot be null");
|
|
|
|
|
|
|
|
if (json.isJsonArray())
|
|
|
|
{
|
|
|
|
List<Ingredient> ingredients = Lists.newArrayList();
|
|
|
|
List<ItemStack> vanilla = Lists.newArrayList();
|
|
|
|
json.getAsJsonArray().forEach((ele) -> {
|
|
|
|
Ingredient ing = CraftingHelper.getIngredient(ele, context);
|
|
|
|
|
|
|
|
if (ing.getClass() == Ingredient.class) {
|
|
|
|
//Vanilla, Due to how we read it splits each itemstack, so we pull out to re-merge later
|
2017-06-18 01:06:04 +00:00
|
|
|
for (ItemStack stack : ing.getMatchingStacks())
|
2017-06-14 17:14:56 +00:00
|
|
|
vanilla.add(stack);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ingredients.add(ing);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!vanilla.isEmpty())
|
|
|
|
{
|
|
|
|
ItemStack[] items = vanilla.toArray(new ItemStack[vanilla.size()]);
|
2017-06-18 01:06:04 +00:00
|
|
|
ingredients.add(Ingredient.fromStacks(items));
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ingredients.size() == 0)
|
|
|
|
throw new JsonSyntaxException("Item array cannot be empty, at least one item must be defined");
|
|
|
|
|
|
|
|
if (ingredients.size() == 1)
|
|
|
|
return ingredients.get(0);
|
|
|
|
|
|
|
|
return new CompoundIngredient(ingredients);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!json.isJsonObject())
|
|
|
|
throw new JsonSyntaxException("Expcted ingredient to be a object or array of objects");
|
|
|
|
|
|
|
|
JsonObject obj = (JsonObject)json;
|
|
|
|
|
|
|
|
String type = context.appendModId(JsonUtils.getString(obj, "type", "minecraft:item"));
|
|
|
|
if (type.isEmpty())
|
|
|
|
throw new JsonSyntaxException("Ingredient type can not be an empty string");
|
|
|
|
|
|
|
|
if (type.equals("minecraft:item"))
|
|
|
|
{
|
|
|
|
String item = JsonUtils.getString(obj, "item");
|
|
|
|
if (item.startsWith("#"))
|
|
|
|
{
|
|
|
|
Ingredient constant = context.getConstant(item.substring(1));
|
|
|
|
if (constant == null)
|
|
|
|
throw new JsonSyntaxException("Ingredient referenced invalid constant: " + item);
|
|
|
|
return constant;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
IIngredientFactory factory = ingredients.get(new ResourceLocation(type));
|
|
|
|
if (factory == null)
|
|
|
|
throw new JsonSyntaxException("Unknown ingredient type: " + type);
|
|
|
|
|
|
|
|
return factory.parse(context, obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static ItemStack getItemStack(JsonObject json, JsonContext context)
|
|
|
|
{
|
|
|
|
String itemName = context.appendModId(JsonUtils.getString(json, "item"));
|
|
|
|
|
|
|
|
Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName));
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
throw new JsonSyntaxException("Unknown item '" + itemName + "'");
|
|
|
|
|
|
|
|
if (item.getHasSubtypes() && !json.has("data"))
|
|
|
|
throw new JsonParseException("Missing data for item '" + itemName + "'");
|
|
|
|
|
|
|
|
if (json.has("nbt"))
|
|
|
|
{
|
|
|
|
// Lets hope this works? Needs test
|
|
|
|
try
|
|
|
|
{
|
|
|
|
NBTTagCompound nbt = JsonToNBT.getTagFromJson(GSON.toJson(json.get("nbt")));
|
|
|
|
NBTTagCompound tmp = new NBTTagCompound();
|
|
|
|
if (nbt.hasKey("ForgeCaps"))
|
|
|
|
{
|
|
|
|
tmp.setTag("ForgeCaps", nbt.getTag("ForgeCaps"));
|
|
|
|
nbt.removeTag("ForgeCaps");
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp.setTag("tag", nbt);
|
|
|
|
tmp.setString("id", itemName);
|
|
|
|
tmp.setInteger("Count", JsonUtils.getInt(json, "count", 1));
|
2017-06-15 23:07:55 +00:00
|
|
|
tmp.setInteger("Damage", JsonUtils.getInt(json, "data", 0));
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
return new ItemStack(tmp);
|
|
|
|
}
|
|
|
|
catch (NBTException e)
|
|
|
|
{
|
|
|
|
throw new JsonSyntaxException("Invalid NBT Entry: " + e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new ItemStack(item, JsonUtils.getInt(json, "count", 1), JsonUtils.getInt(json, "data", 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static ItemStack getItemStackBasic(JsonObject json, JsonContext context)
|
|
|
|
{
|
|
|
|
String itemName = context.appendModId(JsonUtils.getString(json, "item"));
|
|
|
|
|
|
|
|
Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName));
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
throw new JsonSyntaxException("Unknown item '" + itemName + "'");
|
|
|
|
|
|
|
|
if (item.getHasSubtypes() && !json.has("data"))
|
|
|
|
throw new JsonParseException("Missing data for item '" + itemName + "'");
|
|
|
|
|
|
|
|
return new ItemStack(item, 1, JsonUtils.getInt(json, "data", 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class ShapedPrimer {
|
|
|
|
public int height, width;
|
|
|
|
public boolean mirrored = true;
|
|
|
|
public NonNullList<Ingredient> input;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static ShapedPrimer parseShaped(Object... recipe)
|
|
|
|
{
|
|
|
|
ShapedPrimer ret = new ShapedPrimer();
|
|
|
|
String shape = "";
|
|
|
|
int idx = 0;
|
|
|
|
|
|
|
|
if (recipe[idx] instanceof Boolean)
|
|
|
|
{
|
|
|
|
ret.mirrored = (Boolean)recipe[idx];
|
|
|
|
if (recipe[idx+1] instanceof Object[])
|
|
|
|
recipe = (Object[])recipe[idx+1];
|
|
|
|
else
|
|
|
|
idx = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recipe[idx] instanceof String[])
|
|
|
|
{
|
|
|
|
String[] parts = ((String[])recipe[idx++]);
|
|
|
|
|
|
|
|
for (String s : parts)
|
|
|
|
{
|
|
|
|
ret.width = s.length();
|
|
|
|
shape += s;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.height = parts.length;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while (recipe[idx] instanceof String)
|
|
|
|
{
|
|
|
|
String s = (String)recipe[idx++];
|
|
|
|
shape += s;
|
|
|
|
ret.width = s.length();
|
|
|
|
ret.height++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret.width * ret.height != shape.length() || shape.length() == 0)
|
|
|
|
{
|
|
|
|
String err = "Invalid shaped recipe: ";
|
|
|
|
for (Object tmp : recipe)
|
|
|
|
{
|
|
|
|
err += tmp + ", ";
|
|
|
|
}
|
|
|
|
throw new RuntimeException(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
HashMap<Character, Ingredient> itemMap = Maps.newHashMap();
|
2017-06-18 01:06:04 +00:00
|
|
|
itemMap.put(' ', Ingredient.EMPTY);
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
for (; idx < recipe.length; idx += 2)
|
|
|
|
{
|
|
|
|
Character chr = (Character)recipe[idx];
|
|
|
|
Object in = recipe[idx + 1];
|
|
|
|
Ingredient ing = CraftingHelper.getIngredient(in);
|
|
|
|
|
2017-06-15 03:25:22 +00:00
|
|
|
if (' ' == chr.charValue())
|
|
|
|
throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
|
|
|
|
|
2017-06-14 17:14:56 +00:00
|
|
|
if (ing != null)
|
|
|
|
{
|
|
|
|
itemMap.put(chr, ing);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
String err = "Invalid shaped ore recipe: ";
|
|
|
|
for (Object tmp : recipe)
|
|
|
|
{
|
|
|
|
err += tmp + ", ";
|
|
|
|
}
|
|
|
|
throw new RuntimeException(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-18 01:06:04 +00:00
|
|
|
ret.input = NonNullList.withSize(ret.width * ret.height, Ingredient.EMPTY);
|
2017-06-14 22:59:59 +00:00
|
|
|
|
|
|
|
Set<Character> keys = Sets.newHashSet(itemMap.keySet());
|
|
|
|
keys.remove(' ');
|
|
|
|
|
2017-06-14 17:14:56 +00:00
|
|
|
int x = 0;
|
|
|
|
for (char chr : shape.toCharArray())
|
|
|
|
{
|
2017-06-14 22:59:59 +00:00
|
|
|
Ingredient ing = itemMap.get(chr);
|
|
|
|
if (ing == null)
|
|
|
|
throw new IllegalArgumentException("Pattern references symbol '" + chr + "' but it's not defined in the key");
|
|
|
|
ret.input.set(x++, ing);
|
|
|
|
keys.remove(chr);
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
|
2017-06-14 22:59:59 +00:00
|
|
|
if (!keys.isEmpty())
|
|
|
|
throw new IllegalArgumentException("Key defines symbols that aren't used in pattern: " + keys);
|
|
|
|
|
2017-06-14 17:14:56 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean processConditions(JsonArray conditions, JsonContext context)
|
|
|
|
{
|
|
|
|
for (int x = 0; x < conditions.size(); x++)
|
|
|
|
{
|
|
|
|
if (!conditions.get(x).isJsonObject())
|
|
|
|
throw new JsonSyntaxException("Conditions must be an array of JsonObjects");
|
|
|
|
|
|
|
|
JsonObject json = conditions.get(x).getAsJsonObject();
|
|
|
|
BooleanSupplier cond = CraftingHelper.getCondition(json, context);
|
|
|
|
if (!cond.getAsBoolean())
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static BooleanSupplier getCondition(JsonObject json, JsonContext context)
|
|
|
|
{
|
|
|
|
ResourceLocation type = new ResourceLocation(context.appendModId(JsonUtils.getString(json, "type")));
|
|
|
|
IConditionFactory factory = conditions.get(type);
|
|
|
|
if (factory == null)
|
|
|
|
throw new JsonSyntaxException("Unknown condition type: " + type.toString());
|
|
|
|
return factory.parse(context, json);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static IRecipe getRecipe(JsonObject json, JsonContext context)
|
|
|
|
{
|
|
|
|
if (json == null || json.isJsonNull())
|
|
|
|
throw new JsonSyntaxException("Json cannot be null");
|
|
|
|
if (context == null)
|
|
|
|
throw new IllegalArgumentException("getRecipe Context cannot be null");
|
|
|
|
|
|
|
|
String type = context.appendModId(JsonUtils.getString(json, "type"));
|
|
|
|
if (type.isEmpty())
|
|
|
|
throw new JsonSyntaxException("Recipe type can not be an empty string");
|
|
|
|
|
|
|
|
IRecipeFactory factory = recipes.get(new ResourceLocation(type));
|
|
|
|
if (factory == null)
|
|
|
|
throw new JsonSyntaxException("Unknown recipe type: " + type);
|
|
|
|
|
|
|
|
return factory.parse(context, json);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=======================================================
|
|
|
|
// INTERNAL
|
|
|
|
//=======================================================
|
|
|
|
|
|
|
|
private static void init()
|
|
|
|
{
|
|
|
|
conditions.clear();
|
|
|
|
ingredients.clear();
|
|
|
|
recipes.clear();
|
|
|
|
|
|
|
|
registerC("forge:mod_loaded", (context, json) -> {
|
|
|
|
String modid = JsonUtils.getString(json, "modid");
|
|
|
|
return () -> Loader.isModLoaded(modid);
|
|
|
|
});
|
|
|
|
registerC("minecraft:item_exists", (context, json) -> {
|
|
|
|
String itemName = context.appendModId(JsonUtils.getString(json, "item"));
|
|
|
|
return () -> ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName));
|
|
|
|
});
|
|
|
|
registerC("forge:not", (context, json) -> {
|
|
|
|
BooleanSupplier child = CraftingHelper.getCondition(JsonUtils.getJsonObject(json, "value"), context);
|
|
|
|
return () -> !child.getAsBoolean();
|
|
|
|
});
|
|
|
|
registerC("forge:or", (context, json) -> {
|
|
|
|
JsonArray values = JsonUtils.getJsonArray(json, "values");
|
|
|
|
List<BooleanSupplier> children = Lists.newArrayList();
|
|
|
|
for (JsonElement j : values)
|
|
|
|
{
|
|
|
|
if (!j.isJsonObject())
|
|
|
|
throw new JsonSyntaxException("Or condition values must be an array of JsonObjects");
|
|
|
|
children.add(CraftingHelper.getCondition(j.getAsJsonObject(), context));
|
|
|
|
}
|
2017-06-28 07:14:10 +00:00
|
|
|
return () -> children.stream().anyMatch(BooleanSupplier::getAsBoolean);
|
2017-06-14 17:14:56 +00:00
|
|
|
});
|
2017-06-20 09:19:14 +00:00
|
|
|
registerC("forge:and", (context, json) -> {
|
|
|
|
JsonArray values = JsonUtils.getJsonArray(json, "values");
|
|
|
|
List<BooleanSupplier> children = Lists.newArrayList();
|
|
|
|
for (JsonElement j : values)
|
|
|
|
{
|
|
|
|
if (!j.isJsonObject())
|
|
|
|
throw new JsonSyntaxException("And condition values must be an array of JsonObjects");
|
|
|
|
children.add(CraftingHelper.getCondition(j.getAsJsonObject(), context));
|
|
|
|
}
|
|
|
|
return () -> children.stream().allMatch(c -> c.getAsBoolean());
|
|
|
|
});
|
|
|
|
registerC("forge:false", (context, json) -> {
|
|
|
|
return () -> false;
|
|
|
|
});
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
registerR("minecraft:crafting_shaped", (context, json) -> {
|
|
|
|
String group = JsonUtils.getString(json, "group", "");
|
|
|
|
//if (!group.isEmpty() && group.indexOf(':') == -1)
|
|
|
|
// group = context.getModId() + ":" + group;
|
|
|
|
|
|
|
|
Map<Character, Ingredient> ingMap = Maps.newHashMap();
|
|
|
|
for (Entry<String, JsonElement> entry : JsonUtils.getJsonObject(json, "key").entrySet())
|
|
|
|
{
|
|
|
|
if (entry.getKey().length() != 1)
|
|
|
|
throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only).");
|
|
|
|
if (" ".equals(entry.getKey()))
|
|
|
|
throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
|
|
|
|
|
|
|
|
ingMap.put(entry.getKey().toCharArray()[0], CraftingHelper.getIngredient(entry.getValue(), context));
|
|
|
|
}
|
2017-06-18 01:06:04 +00:00
|
|
|
ingMap.put(' ', Ingredient.EMPTY);
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
JsonArray patternJ = JsonUtils.getJsonArray(json, "pattern");
|
|
|
|
|
|
|
|
if (patternJ.size() == 0)
|
|
|
|
throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed");
|
|
|
|
if (patternJ.size() > 3)
|
|
|
|
throw new JsonSyntaxException("Invalid pattern: too many rows, 3 is maximum");
|
|
|
|
|
|
|
|
String[] pattern = new String[patternJ.size()];
|
|
|
|
for (int x = 0; x < pattern.length; ++x)
|
|
|
|
{
|
|
|
|
String line = JsonUtils.getString(patternJ.get(x), "pattern[" + x + "]");
|
|
|
|
if (line.length() > 3)
|
|
|
|
throw new JsonSyntaxException("Invalid pattern: too many columns, 3 is maximum");
|
|
|
|
if (x > 0 && pattern[0].length() != line.length())
|
|
|
|
throw new JsonSyntaxException("Invalid pattern: each row must be the same width");
|
|
|
|
pattern[x] = line;
|
|
|
|
}
|
|
|
|
|
2017-06-18 01:06:04 +00:00
|
|
|
NonNullList<Ingredient> input = NonNullList.withSize(pattern[0].length() * pattern.length, Ingredient.EMPTY);
|
2017-06-14 17:14:56 +00:00
|
|
|
Set<Character> keys = Sets.newHashSet(ingMap.keySet());
|
|
|
|
keys.remove(' ');
|
|
|
|
|
|
|
|
int x = 0;
|
|
|
|
for (String line : pattern)
|
|
|
|
{
|
|
|
|
for (char chr : line.toCharArray())
|
|
|
|
{
|
|
|
|
Ingredient ing = ingMap.get(chr);
|
|
|
|
if (ing == null)
|
|
|
|
throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key");
|
|
|
|
input.set(x++, ing);
|
|
|
|
keys.remove(chr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!keys.isEmpty())
|
|
|
|
throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + keys);
|
|
|
|
|
|
|
|
ItemStack result = CraftingHelper.getItemStack(JsonUtils.getJsonObject(json, "result"), context);
|
|
|
|
return new ShapedRecipes(group, pattern[0].length(), pattern.length, input, result);
|
|
|
|
});
|
|
|
|
registerR("minecraft:crafting_shapeless", (context, json) -> {
|
|
|
|
String group = JsonUtils.getString(json, "group", "");
|
|
|
|
|
|
|
|
NonNullList<Ingredient> ings = NonNullList.create();
|
|
|
|
for (JsonElement ele : JsonUtils.getJsonArray(json, "ingredients"))
|
|
|
|
ings.add(CraftingHelper.getIngredient(ele, context));
|
|
|
|
|
|
|
|
if (ings.isEmpty())
|
|
|
|
throw new JsonParseException("No ingredients for shapeless recipe");
|
|
|
|
if (ings.size() > 9)
|
|
|
|
throw new JsonParseException("Too many ingredients for shapeless recipe");
|
|
|
|
|
2017-06-19 23:49:21 +00:00
|
|
|
ItemStack itemstack = CraftingHelper.getItemStack(JsonUtils.getJsonObject(json, "result"), context);
|
2017-06-14 17:14:56 +00:00
|
|
|
return new ShapelessRecipes(group, itemstack, ings);
|
|
|
|
});
|
|
|
|
registerR("forge:ore_shaped", ShapedOreRecipe::factory);
|
|
|
|
registerR("forge:ore_shapeless", ShapelessOreRecipe::factory);
|
|
|
|
|
2017-06-18 01:06:04 +00:00
|
|
|
registerI("minecraft:item", (context, json) -> Ingredient.fromStacks(CraftingHelper.getItemStackBasic(json, context)));
|
|
|
|
registerI("minecraft:empty", (context, json) -> Ingredient.EMPTY);
|
2017-06-24 21:47:48 +00:00
|
|
|
registerI("minecraft:item_nbt", (context, json) -> new IngredientNBT(CraftingHelper.getItemStack(json, context)));
|
2017-06-14 17:14:56 +00:00
|
|
|
registerI("forge:ore_dict", (context, json) -> new OreIngredient(JsonUtils.getString(json, "ore")));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void registerC(String name, IConditionFactory fac) {
|
|
|
|
register(new ResourceLocation(name), fac);
|
|
|
|
}
|
|
|
|
private static void registerR(String name, IRecipeFactory fac) {
|
|
|
|
register(new ResourceLocation(name), fac);
|
|
|
|
}
|
|
|
|
private static void registerI(String name, IIngredientFactory fac) {
|
|
|
|
register(new ResourceLocation(name), fac);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void loadFactories(JsonObject json, JsonContext context)
|
|
|
|
{
|
|
|
|
if (json.has("ingredients"))
|
|
|
|
{
|
|
|
|
for (Entry<String, JsonElement> entry : JsonUtils.getJsonObject(json, "ingredients").entrySet())
|
|
|
|
{
|
|
|
|
ResourceLocation key = new ResourceLocation(context.getModId(), entry.getKey());
|
|
|
|
String clsName = JsonUtils.getString(entry.getValue(), "ingredients[" + entry.getValue() + "]");
|
2017-06-17 03:24:58 +00:00
|
|
|
register(key, getClassInstance(clsName, IIngredientFactory.class));
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (json.has("recipes"))
|
|
|
|
{
|
|
|
|
for (Entry<String, JsonElement> entry : JsonUtils.getJsonObject(json, "recipes").entrySet())
|
|
|
|
{
|
|
|
|
ResourceLocation key = new ResourceLocation(context.getModId(), entry.getKey());
|
|
|
|
String clsName = JsonUtils.getString(entry.getValue(), "recipes[" + entry.getValue() + "]");
|
2017-06-17 03:24:58 +00:00
|
|
|
register(key, getClassInstance(clsName, IRecipeFactory.class));
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-17 03:24:58 +00:00
|
|
|
|
|
|
|
if (json.has("conditions"))
|
|
|
|
{
|
|
|
|
for (Entry<String, JsonElement> entry : JsonUtils.getJsonObject(json, "conditions").entrySet())
|
|
|
|
{
|
|
|
|
ResourceLocation key = new ResourceLocation(context.getModId(), entry.getKey());
|
|
|
|
String clsName = JsonUtils.getString(entry.getValue(), "conditions[" + entry.getValue() + "]");
|
|
|
|
register(key, getClassInstance(clsName, IConditionFactory.class));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static <T> T getClassInstance(String clsName, Class<T> expected)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Class<?> cls = Class.forName(clsName);
|
|
|
|
if (!expected.isAssignableFrom(cls))
|
|
|
|
throw new JsonSyntaxException("Class '" + clsName + "' is not an " + expected.getSimpleName());
|
|
|
|
return (T)cls.newInstance();
|
|
|
|
}
|
|
|
|
catch (ClassNotFoundException e)
|
|
|
|
{
|
|
|
|
throw new JsonSyntaxException("Could not find " + expected.getSimpleName() + ": " + clsName, e);
|
|
|
|
}
|
|
|
|
catch (InstantiationException | IllegalAccessException e)
|
|
|
|
{
|
|
|
|
throw new JsonSyntaxException("Could not instantiate " + expected.getSimpleName() + ": " + clsName, e);
|
|
|
|
}
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
|
2017-06-24 00:52:59 +00:00
|
|
|
public static void loadRecipes(boolean revertFrozen)
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
|
|
|
//TODO: If this errors in ServerInit it freezes the client at loading world, find a way to pop that up?
|
|
|
|
//TODO: Figure out how to remove recipes, and override them. This relies on cpw to help.
|
|
|
|
//For now this is only done one after mod init, I want to move this to ServerInit and re-do it many times.
|
|
|
|
init();
|
2017-06-19 22:02:18 +00:00
|
|
|
ForgeRegistry<IRecipe> reg = (ForgeRegistry<IRecipe>)ForgeRegistries.RECIPES;
|
2017-06-24 00:52:59 +00:00
|
|
|
//reg.unfreeze();
|
2017-06-14 17:14:56 +00:00
|
|
|
if (DEBUG_LOAD_MINECRAFT)
|
2017-06-19 22:02:18 +00:00
|
|
|
reg.clear();
|
2017-06-24 00:52:59 +00:00
|
|
|
else if (revertFrozen)
|
2017-06-19 22:02:18 +00:00
|
|
|
GameData.revert(RegistryManager.FROZEN, GameData.RECIPES, false);
|
2017-06-14 17:14:56 +00:00
|
|
|
//ModContainer old = Loader.instance().activeModContainer();
|
|
|
|
Loader.instance().setActiveModContainer(null);
|
2017-06-28 07:14:10 +00:00
|
|
|
Loader.instance().getActiveModList().forEach(CraftingHelper::loadFactories);
|
|
|
|
Loader.instance().getActiveModList().forEach(CraftingHelper::loadRecipes);
|
2017-06-14 17:14:56 +00:00
|
|
|
Loader.instance().setActiveModContainer(null);
|
2017-07-13 21:45:13 +00:00
|
|
|
|
|
|
|
GameData.fireRegistryEvents(rl -> rl.equals(GameData.RECIPES));
|
|
|
|
|
2017-06-24 00:52:59 +00:00
|
|
|
//reg.freeze();
|
2017-06-24 22:34:09 +00:00
|
|
|
FMLCommonHandler.instance().resetClientRecipeBook();
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static void loadFactories(ModContainer mod)
|
|
|
|
{
|
|
|
|
FileSystem fs = null;
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
JsonContext ctx = new JsonContext(mod.getModId());
|
|
|
|
Path fPath = null;
|
2017-06-20 00:18:53 +00:00
|
|
|
if (mod.getSource().isFile())
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-20 00:18:53 +00:00
|
|
|
fs = FileSystems.newFileSystem(mod.getSource().toPath(), null);
|
2017-06-14 17:14:56 +00:00
|
|
|
fPath = fs.getPath("/assets/" + ctx.getModId() + "/recipes/_factories.json");
|
|
|
|
}
|
2017-06-20 00:18:53 +00:00
|
|
|
else if (mod.getSource().isDirectory())
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-20 00:18:53 +00:00
|
|
|
fPath = mod.getSource().toPath().resolve("assets/" + ctx.getModId() + "/recipes/_factories.json");
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
if (fPath != null && Files.exists(fPath))
|
|
|
|
{
|
|
|
|
reader = Files.newBufferedReader(fPath);
|
2017-06-24 07:56:21 +00:00
|
|
|
JsonObject json = JsonUtils.fromJson(GSON, reader, JsonObject.class);
|
2017-06-14 17:14:56 +00:00
|
|
|
loadFactories(json, ctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (IOException e)
|
|
|
|
{
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
IOUtils.closeQuietly(fs);
|
|
|
|
IOUtils.closeQuietly(reader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean loadRecipes(ModContainer mod)
|
2017-06-27 22:18:52 +00:00
|
|
|
{
|
|
|
|
JsonContext ctx = new JsonContext(mod.getModId());
|
|
|
|
|
|
|
|
return findFiles(mod, "assets/" + mod.getModId() + "/recipes",
|
|
|
|
root ->
|
|
|
|
{
|
|
|
|
Path fPath = root.resolve("_constants.json");
|
|
|
|
if (fPath != null && Files.exists(fPath))
|
|
|
|
{
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
reader = Files.newBufferedReader(fPath);
|
|
|
|
JsonObject[] json = JsonUtils.fromJson(GSON, reader, JsonObject[].class);
|
|
|
|
ctx.loadConstants(json);
|
|
|
|
}
|
|
|
|
catch (IOException e)
|
|
|
|
{
|
|
|
|
FMLLog.log.error("Error loading _constants.json: ", e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
IOUtils.closeQuietly(reader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
(root, file) ->
|
|
|
|
{
|
|
|
|
Loader.instance().setActiveModContainer(mod);
|
|
|
|
|
|
|
|
String relative = root.relativize(file).toString();
|
|
|
|
if (!"json".equals(FilenameUtils.getExtension(file.toString())) || relative.startsWith("_"))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
String name = FilenameUtils.removeExtension(relative).replaceAll("\\\\", "/");
|
|
|
|
ResourceLocation key = new ResourceLocation(ctx.getModId(), name);
|
|
|
|
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
reader = Files.newBufferedReader(file);
|
|
|
|
JsonObject json = JsonUtils.fromJson(GSON, reader, JsonObject.class);
|
|
|
|
if (json.has("conditions") && !CraftingHelper.processConditions(JsonUtils.getJsonArray(json, "conditions"), ctx))
|
|
|
|
return true;
|
|
|
|
IRecipe recipe = CraftingHelper.getRecipe(json, ctx);
|
|
|
|
ForgeRegistries.RECIPES.register(recipe.setRegistryName(key));
|
|
|
|
}
|
|
|
|
catch (JsonParseException e)
|
|
|
|
{
|
|
|
|
FMLLog.log.error("Parsing error loading recipe {}", key, e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch (IOException e)
|
|
|
|
{
|
|
|
|
FMLLog.log.error("Couldn't read recipe {} from {}", key, file, e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
IOUtils.closeQuietly(reader);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static boolean findFiles(ModContainer mod, String base, Function<Path, Boolean> preprocessor, BiFunction<Path, Path, Boolean> processor)
|
2017-07-14 23:10:36 +00:00
|
|
|
{
|
|
|
|
return findFiles(mod, base, preprocessor, processor, false);
|
|
|
|
}
|
|
|
|
public static boolean findFiles(ModContainer mod, String base, Function<Path, Boolean> preprocessor, BiFunction<Path, Path, Boolean> processor, boolean defaultUnfoundRoot)
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
|
|
|
FileSystem fs = null;
|
|
|
|
try
|
|
|
|
{
|
2017-06-20 00:18:53 +00:00
|
|
|
File source = mod.getSource();
|
2017-06-14 17:14:56 +00:00
|
|
|
|
2017-07-14 23:10:36 +00:00
|
|
|
if ("minecraft".equals(mod.getModId()))
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-07-14 23:10:36 +00:00
|
|
|
if (!DEBUG_LOAD_MINECRAFT)
|
|
|
|
return true;
|
|
|
|
|
2017-06-14 17:14:56 +00:00
|
|
|
try
|
|
|
|
{
|
2017-06-20 00:18:53 +00:00
|
|
|
URI tmp = CraftingManager.class.getResource("/assets/.mcassetsroot").toURI();
|
|
|
|
source = new File(tmp.resolve("..").getPath());
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
catch (URISyntaxException e)
|
|
|
|
{
|
2017-06-24 08:46:05 +00:00
|
|
|
FMLLog.log.error("Error finding Minecraft jar: ", e);
|
2017-06-14 17:14:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Path root = null;
|
2017-06-20 00:18:53 +00:00
|
|
|
if (source.isFile())
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2017-06-20 00:18:53 +00:00
|
|
|
fs = FileSystems.newFileSystem(source.toPath(), null);
|
2017-06-27 22:18:52 +00:00
|
|
|
root = fs.getPath("/" + base);
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
catch (IOException e)
|
|
|
|
{
|
2017-06-24 08:46:05 +00:00
|
|
|
FMLLog.log.error("Error loading FileSystem from jar: ", e);
|
2017-06-14 17:14:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2017-06-20 00:18:53 +00:00
|
|
|
else if (source.isDirectory())
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
root = source.toPath().resolve(base);
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (root == null || !Files.exists(root))
|
2017-07-14 23:10:36 +00:00
|
|
|
return defaultUnfoundRoot;
|
2017-06-14 17:14:56 +00:00
|
|
|
|
2017-06-27 22:18:52 +00:00
|
|
|
if (preprocessor != null)
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
Boolean cont = preprocessor.apply(root);
|
|
|
|
if (cont == null || !cont.booleanValue())
|
2017-06-14 17:14:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-27 22:18:52 +00:00
|
|
|
if (processor != null)
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
Iterator<Path> itr = null;
|
2017-06-14 17:14:56 +00:00
|
|
|
try
|
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
itr = Files.walk(root).iterator();
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
2017-06-27 22:18:52 +00:00
|
|
|
catch (IOException e)
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
FMLLog.log.error("Error iterating filesystem for: {}", mod.getModId(), e);
|
2017-06-14 17:14:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-06-27 22:18:52 +00:00
|
|
|
|
|
|
|
while (itr != null && itr.hasNext())
|
2017-06-14 17:14:56 +00:00
|
|
|
{
|
2017-06-27 22:18:52 +00:00
|
|
|
Boolean cont = processor.apply(root, itr.next());
|
|
|
|
if (cont == null || !cont.booleanValue())
|
|
|
|
return false;
|
2017-06-14 17:14:56 +00:00
|
|
|
}
|
2017-06-27 22:18:52 +00:00
|
|
|
}
|
2017-06-14 17:14:56 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
IOUtils.closeQuietly(fs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|