More work on recipes, custom ingredients, recipes, and constants should work now.

This commit is contained in:
LexManos 2018-09-17 03:41:16 -07:00
parent 03d19f2e70
commit 02c31cc867
22 changed files with 599 additions and 683 deletions

View File

@ -25,3 +25,23 @@
public String getGroup() {
return this.field_201833_b;
}
@@ -75,6 +71,7 @@
}
public static class Serializer implements IRecipeSerializer<FurnaceRecipe> {
+ private static ResourceLocation NAME = new ResourceLocation("minecraft", "smelting");
public FurnaceRecipe func_199425_a_(ResourceLocation p_199425_1_, JsonObject p_199425_2_) {
String s = JsonUtils.getString(p_199425_2_, "group", "");
Ingredient ingredient;
@@ -113,8 +110,9 @@
p_199427_1_.writeVarInt(p_199427_2_.field_201837_f);
}
- public String func_199567_a() {
- return "smelting";
+ @Override
+ public ResourceLocation getName() {
+ return NAME;
}
}
}

View File

@ -0,0 +1,15 @@
--- a/net/minecraft/item/crafting/IRecipeSerializer.java
+++ b/net/minecraft/item/crafting/IRecipeSerializer.java
@@ -11,5 +11,11 @@
void func_199427_a_(PacketBuffer p_199427_1_, T p_199427_2_);
- String func_199567_a();
+ @Deprecated //Modders, do not use, this is un-namespaced and thus could cause clashes.
+ default String func_199567_a() {
+ ResourceLocation name = getName(); //To keep compatibility with vanilla, anything in the "minecraft" namespace doesn't get a prefix.
+ return name.getNamespace().equals("minecraft") ? name.getPath() : name.toString();
+ }
+
+ ResourceLocation getName();
}

View File

@ -7,7 +7,7 @@
-import net.minecraftforge.api.distmarker.Dist;
-import net.minecraftforge.api.distmarker.OnlyIn;
public final class Ingredient implements Predicate<ItemStack> {
public class Ingredient implements Predicate<ItemStack> {
+ //Because Mojang caches things... we need to invalidate them.. so... here we go..
+ private static final java.util.Set<Ingredient> INSTANCES = java.util.Collections.newSetFromMap(new java.util.WeakHashMap<Ingredient, Boolean>());
+ public static void invalidateAll() {
@ -23,7 +23,7 @@
private IntList matchingStacksPacked;
+ private final boolean isSimple;
private Ingredient(Stream<? extends Ingredient.IItemList> p_i49381_1_) {
protected Ingredient(Stream<? extends Ingredient.IItemList> p_i49381_1_) {
this.field_199807_b = (Ingredient.IItemList[])p_i49381_1_.filter(field_209362_b).toArray((p_209360_0_) -> {
return new Ingredient.IItemList[p_209360_0_];
});
@ -35,7 +35,18 @@
public ItemStack[] getMatchingStacks() {
this.func_199806_d();
return this.matchingStacks;
@@ -121,6 +127,15 @@
@@ -95,6 +101,10 @@
public final void func_199564_a(PacketBuffer p_199564_1_) {
this.func_199806_d();
+ if (!this.isVanilla()) {
+ net.minecraftforge.common.crafting.CraftingHelper.write(p_199564_1_, this);
+ return;
+ }
p_199564_1_.writeVarInt(this.matchingStacks.length);
for(int i = 0; i < this.matchingStacks.length; ++i) {
@@ -121,6 +131,25 @@
return this.field_199807_b.length == 0 && (this.matchingStacks == null || this.matchingStacks.length == 0) && (this.matchingStacksPacked == null || this.matchingStacksPacked.isEmpty());
}
@ -48,10 +59,20 @@
+ return isSimple || this == EMPTY;
+ }
+
private static Ingredient func_209357_a(Stream<? extends Ingredient.IItemList> p_209357_0_) {
+ private final boolean isVanilla = this.getClass() == Ingredient.class;
+ public final boolean isVanilla() {
+ return isVanilla;
+ }
+
+ public net.minecraftforge.common.crafting.IIngredientSerializer<? extends Ingredient> getSerializer() {
+ if (!isVanilla()) throw new IllegalStateException("Modderrs must implement Ingredient.getSerializer in their custom Ingredients: " + this);
+ return net.minecraftforge.common.crafting.CraftingHelper.INGREDIENT_VANILLA;
+ }
+
public static Ingredient func_209357_a(Stream<? extends Ingredient.IItemList> p_209357_0_) {
Ingredient ingredient = new Ingredient(p_209357_0_);
return ingredient.field_199807_b.length == 0 ? EMPTY : ingredient;
@@ -132,7 +147,6 @@
@@ -132,7 +161,6 @@
}));
}
@ -59,3 +80,50 @@
public static Ingredient fromStacks(ItemStack... stacks) {
return func_209357_a(Arrays.stream(stacks).map((p_209356_0_) -> {
return new Ingredient.SingleItemList(p_209356_0_);
@@ -144,7 +172,12 @@
}
public static Ingredient func_199566_b(PacketBuffer p_199566_0_) {
+ p_199566_0_.markReaderIndex();
int i = p_199566_0_.readVarInt();
+ if (i == -1) {
+ p_199566_0_.resetReaderIndex();
+ return net.minecraftforge.common.crafting.CraftingHelper.getIngredient(p_199566_0_.readResourceLocation(), p_199566_0_);
+ }
return func_209357_a(Stream.generate(() -> {
return new Ingredient.SingleItemList(p_199566_0_.readItemStack());
}).limit((long)i));
@@ -152,6 +185,8 @@
public static Ingredient func_199802_a(@Nullable JsonElement p_199802_0_) {
if (p_199802_0_ != null && !p_199802_0_.isJsonNull()) {
+ Ingredient ret = net.minecraftforge.common.crafting.CraftingHelper.getIngredient(p_199802_0_);
+ if (ret != null) return ret;
if (p_199802_0_.isJsonObject()) {
return func_209357_a(Stream.of(func_199803_a(p_199802_0_.getAsJsonObject())));
} else if (p_199802_0_.isJsonArray()) {
@@ -172,6 +207,11 @@
}
public static Ingredient.IItemList func_199803_a(JsonObject p_199803_0_) {
+ if (p_199803_0_.has("constant")) {
+ Ingredient.IItemList ret = net.minecraftforge.common.crafting.CraftingHelper.getConstant(new ResourceLocation(JsonUtils.getString(p_199803_0_, "constant")));
+ if (ret == null) throw new JsonSyntaxException("Ingredient referenced invalid constant: " + JsonUtils.getString(p_199803_0_, "constant"));
+ return ret;
+ }
if (p_199803_0_.has("item") && p_199803_0_.has("tag")) {
throw new JsonParseException("An ingredient entry is either a tag or an item, not both");
} else if (p_199803_0_.has("item")) {
@@ -195,6 +235,12 @@
}
}
+ //Merges several vanilla Ingredients together. As a qwerk of how the json is structured, we can't tell if its a single Ingredient type or multiple so we split per item and remerge here.
+ //Only public for internal use, so we can access a private field in here.
+ public static Ingredient merge(Collection<Ingredient> parts) {
+ return func_209357_a(parts.stream().flatMap(i -> Arrays.stream(i.field_199807_b)));
+ }
+
public interface IItemList {
Collection<ItemStack> func_199799_a();

View File

@ -0,0 +1,20 @@
--- a/net/minecraft/item/crafting/RecipeManager.java
+++ b/net/minecraft/item/crafting/RecipeManager.java
@@ -37,6 +37,8 @@
this.field_199523_e = false;
this.field_199522_d.clear();
+ net.minecraftforge.common.crafting.CraftingHelper.reloadConstants(p_195410_1_);
+
for(ResourceLocation resourcelocation : p_195410_1_.func_199003_a("recipes", (p_199516_0_) -> {
return p_199516_0_.endsWith(".json");
})) {
@@ -47,6 +49,8 @@
JsonObject jsonobject = (JsonObject)JsonUtils.gsonDeserialize(gson, IOUtils.toString(iresource.func_199027_b(), StandardCharsets.UTF_8), JsonObject.class);
if (jsonobject == null) {
field_199521_c.error("Couldn't load recipe {} as it's null or empty", (Object)resourcelocation1);
+ } else if (jsonobject.has("conditions") && !net.minecraftforge.common.crafting.CraftingHelper.processConditions(JsonUtils.getJsonArray(jsonobject, "conditions"))) {
+ field_199521_c.info("Skipping loading recipe {} as it's conditions were not met", resourcelocation1);
} else {
this.func_199509_a(RecipeSerializers.func_199572_a(resourcelocation1, jsonobject));
}

View File

@ -0,0 +1,69 @@
--- a/net/minecraft/item/crafting/RecipeSerializers.java
+++ b/net/minecraft/item/crafting/RecipeSerializers.java
@@ -10,7 +10,7 @@
import net.minecraft.util.ResourceLocation;
public class RecipeSerializers {
- private static final Map<String, IRecipeSerializer<?>> field_199590_p = Maps.<String, IRecipeSerializer<?>>newHashMap();
+ private static final Map<ResourceLocation, IRecipeSerializer<?>> field_199590_p = Maps.newHashMap();
public static final IRecipeSerializer<ShapedRecipe> field_199575_a = func_199573_a(new ShapedRecipe.Serializer());
public static final IRecipeSerializer<ShapelessRecipe> field_199576_b = func_199573_a(new ShapelessRecipe.Serializer());
public static final RecipeSerializers.SimpleSerializer<RecipesArmorDyes> field_199577_c = func_199573_a(new RecipeSerializers.SimpleSerializer<RecipesArmorDyes>("crafting_special_armordye", RecipesArmorDyes::new));
@@ -27,18 +27,19 @@
public static final RecipeSerializers.SimpleSerializer<ShieldRecipes> field_199588_n = func_199573_a(new RecipeSerializers.SimpleSerializer<ShieldRecipes>("crafting_special_shielddecoration", ShieldRecipes::new));
public static final RecipeSerializers.SimpleSerializer<ShulkerBoxColoringRecipe> field_199589_o = func_199573_a(new RecipeSerializers.SimpleSerializer<ShulkerBoxColoringRecipe>("crafting_special_shulkerboxcoloring", ShulkerBoxColoringRecipe::new));
public static final IRecipeSerializer<FurnaceRecipe> field_201839_p = func_199573_a(new FurnaceRecipe.Serializer());
+ private static final java.util.Set<ResourceLocation> VANILLA_TYPES = new java.util.HashSet<>(field_199590_p.keySet()); //Copy of vanilla so we can keep track for compatibility.
public static <S extends IRecipeSerializer<T>, T extends IRecipe> S func_199573_a(S p_199573_0_) {
- if (field_199590_p.containsKey(p_199573_0_.func_199567_a())) {
- throw new IllegalArgumentException("Duplicate recipe serializer " + p_199573_0_.func_199567_a());
+ if (field_199590_p.containsKey(p_199573_0_.getName())) {
+ throw new IllegalArgumentException("Duplicate recipe serializer " + p_199573_0_.getName());
} else {
- field_199590_p.put(p_199573_0_.func_199567_a(), p_199573_0_);
+ field_199590_p.put(p_199573_0_.getName(), p_199573_0_);
return p_199573_0_;
}
}
public static IRecipe func_199572_a(ResourceLocation p_199572_0_, JsonObject p_199572_1_) {
- String s = JsonUtils.getString(p_199572_1_, "type");
+ ResourceLocation s = new ResourceLocation(JsonUtils.getString(p_199572_1_, "type"));
IRecipeSerializer<?> irecipeserializer = field_199590_p.get(s);
if (irecipeserializer == null) {
throw new JsonSyntaxException("Invalid or unsupported recipe type '" + s + "'");
@@ -49,7 +50,7 @@
public static IRecipe func_199571_a(PacketBuffer p_199571_0_) {
ResourceLocation resourcelocation = p_199571_0_.readResourceLocation();
- String s = p_199571_0_.readString(32767);
+ ResourceLocation s = new ResourceLocation(p_199571_0_.readString(32767));
IRecipeSerializer<?> irecipeserializer = field_199590_p.get(s);
if (irecipeserializer == null) {
throw new IllegalArgumentException("Unknown recipe serializer " + s);
@@ -68,10 +69,12 @@
public static final class SimpleSerializer<T extends IRecipe> implements IRecipeSerializer<T> {
private final String field_199569_a;
private final Function<ResourceLocation, T> field_199570_b;
+ private final ResourceLocation name;
public SimpleSerializer(String p_i48188_1_, Function<ResourceLocation, T> p_i48188_2_) {
this.field_199569_a = p_i48188_1_;
this.field_199570_b = p_i48188_2_;
+ this.name = new ResourceLocation(field_199569_a);
}
public T func_199425_a_(ResourceLocation p_199425_1_, JsonObject p_199425_2_) {
@@ -85,8 +88,9 @@
public void func_199427_a_(PacketBuffer p_199427_1_, T p_199427_2_) {
}
- public String func_199567_a() {
- return this.field_199569_a;
+ @Override
+ public ResourceLocation getName() {
+ return this.name;
}
}
}

View File

@ -14,7 +14,7 @@
itemstack = itemstack1;
} else {
- if (!(itemstack1.getItem() instanceof ItemDye)) {
+ if (!itemstack1.getItem().func_206844_a(net.minecraft.tags.ItemTags.DYES)) {
+ if (!itemstack1.getItem().func_206844_a(net.minecraftforge.common.Tags.Items.DYES)) {
return false;
}

View File

@ -7,7 +7,8 @@
-import net.minecraftforge.api.distmarker.Dist;
-import net.minecraftforge.api.distmarker.OnlyIn;
public class ShapedRecipe implements IRecipe {
-public class ShapedRecipe implements IRecipe {
+public class ShapedRecipe implements IRecipe, net.minecraftforge.common.crafting.IShapedRecipe {
+ static int MAX_WIDTH = 3;
+ static int MAX_HEIGHT = 3;
+ /**
@ -40,7 +41,28 @@
public boolean canFit(int width, int height) {
return width >= this.recipeWidth && height >= this.recipeHeight;
}
@@ -202,15 +211,15 @@
@@ -117,10 +126,20 @@
return this.recipeWidth;
}
+ @Override
+ public int getRecipeWidth() {
+ return getWidth();
+ }
+
public int getHeight() {
return this.recipeHeight;
}
+ @Override
+ public int getRecipeHeight() {
+ return getHeight();
+ }
+
private static NonNullList<Ingredient> deserializeIngredients(String[] pattern, Map<String, Ingredient> keys, int patternWidth, int patternHeight) {
NonNullList<Ingredient> nonnulllist = NonNullList.<Ingredient>withSize(patternWidth * patternHeight, Ingredient.EMPTY);
Set<String> set = Sets.newHashSet(keys.keySet());
@@ -202,15 +221,15 @@
private static String[] patternFromJson(JsonArray jsonArr) {
String[] astring = new String[jsonArr.size()];
@ -60,3 +82,23 @@
}
if (i > 0 && astring[0].length() != s.length()) {
@@ -257,6 +276,7 @@
}
public static class Serializer implements IRecipeSerializer<ShapedRecipe> {
+ private static final ResourceLocation NAME = new ResourceLocation("minecraft", "crafting_shaped");
public ShapedRecipe func_199425_a_(ResourceLocation p_199425_1_, JsonObject p_199425_2_) {
String s = JsonUtils.getString(p_199425_2_, "group", "");
Map<String, Ingredient> map = ShapedRecipe.deserializeKey(JsonUtils.getJsonObject(p_199425_2_, "key"));
@@ -268,8 +288,9 @@
return new ShapedRecipe(p_199425_1_, s, i, j, nonnulllist, itemstack);
}
- public String func_199567_a() {
- return "crafting_shaped";
+ @Override
+ public ResourceLocation getName() {
+ return NAME;
}
public ShapedRecipe func_199426_a_(ResourceLocation p_199426_1_, PacketBuffer p_199426_2_) {

View File

@ -56,7 +56,7 @@
}
}
@@ -74,7 +77,6 @@
@@ -74,19 +77,19 @@
return this.recipeOutput.copy();
}
@ -64,7 +64,11 @@
public boolean canFit(int width, int height) {
return width * height >= this.recipeItems.size();
}
@@ -85,8 +87,8 @@
public static class Serializer implements IRecipeSerializer<ShapelessRecipe> {
+ private static final ResourceLocation NAME = new ResourceLocation("minecraft", "crafting_shapeless");
public ShapelessRecipe func_199425_a_(ResourceLocation p_199425_1_, JsonObject p_199425_2_) {
String s = JsonUtils.getString(p_199425_2_, "group", "");
NonNullList<Ingredient> nonnulllist = func_199568_a(JsonUtils.getJsonArray(p_199425_2_, "ingredients"));
if (nonnulllist.isEmpty()) {
throw new JsonParseException("No ingredients for shapeless recipe");
@ -75,3 +79,15 @@
} else {
ItemStack itemstack = ShapedRecipe.func_199798_a(JsonUtils.getJsonObject(p_199425_2_, "result"));
return new ShapelessRecipe(p_199425_1_, s, itemstack, nonnulllist);
@@ -106,8 +109,9 @@
return nonnulllist;
}
- public String func_199567_a() {
- return "crafting_shapeless";
+ @Override
+ public ResourceLocation getName() {
+ return NAME;
}
public ShapelessRecipe func_199426_a_(ResourceLocation p_199426_1_, PacketBuffer p_199426_2_) {

View File

@ -14,7 +14,7 @@
++i;
} else {
- if (!(itemstack.getItem() instanceof ItemDye)) {
+ if (!itemstack.getItem().func_206844_a(net.minecraft.tags.ItemTags.DYES)) {
+ if (!itemstack.getItem().func_206844_a(net.minecraftforge.common.Tags.Items.DYES)) {
return false;
}

View File

@ -939,8 +939,8 @@ public class ForgeChunkManager
{
ticket.setInteger("chunkX", MathHelper.floor(tick.entity.chunkCoordX));
ticket.setInteger("chunkZ", MathHelper.floor(tick.entity.chunkCoordZ));
ticket.setLong("PersistentIDMSB", tick.entity.getPersistentID().getMostSignificantBits());
ticket.setLong("PersistentIDLSB", tick.entity.getPersistentID().getLeastSignificantBits());
ticket.setLong("PersistentIDMSB", tick.entity.getUniqueID().getMostSignificantBits());
ticket.setLong("PersistentIDLSB", tick.entity.getUniqueID().getLeastSignificantBits());
tickets.add(ticket);
}
else if (tick.ticketType != Type.ENTITY)
@ -966,7 +966,7 @@ public class ForgeChunkManager
static void loadEntity(Entity entity)
{
UUID id = entity.getPersistentID();
UUID id = entity.getUniqueID();
Ticket tick = pendingEntities.get(id);
if (tick != null)
{

View File

@ -1014,7 +1014,7 @@ public class ForgeHooks
if (stack.getItem().hasContainerItem(stack))
{
stack = stack.getItem().getContainerItem(stack);
if (!stack.isEmpty() && stack.isItemStackDamageable() && stack.getMetadata() > stack.getMaxDamage())
if (!stack.isEmpty() && stack.isItemStackDamageable() && stack.getItemDamage() > stack.getMaxDamage())
{
ForgeEventFactory.onPlayerDestroyItem(craftingPlayer.get(), stack, null);
return ItemStack.EMPTY;
@ -1035,10 +1035,10 @@ public class ForgeHooks
{
filled = ((IFluidBlock)block).getFilledPercentage(entity.world, pos);
}
else if (block instanceof BlockLiquid)
/*else if (block instanceof BlockLiquid)
{
filled = 1.0 - (BlockLiquid.getLiquidHeightPercent(block.getMetaFromState(state)) - (1.0 / 9.0));
}
}*/
if (filled < 0)
{
@ -1271,13 +1271,6 @@ public class ForgeHooks
public static LootEntry deserializeJsonLootEntry(String type, JsonObject json, int weight, int quality, LootCondition[] conditions){ return null; }
public static String getLootEntryType(LootEntry entry){ return null; } //Companion to above function
/** @deprecated use {@link ForgeEventFactory#onProjectileImpact(EntityThrowable, RayTraceResult)} */
@Deprecated // TODO: remove (1.13)
public static boolean onThrowableImpact(EntityThrowable throwable, RayTraceResult ray)
{
return ForgeEventFactory.onProjectileImpact(throwable, ray);
}
public static boolean onCropsGrowPre(World worldIn, BlockPos pos, IBlockState state, boolean def)
{
BlockEvent ev = new BlockEvent.CropGrowEvent.Pre(worldIn,pos,state);

View File

@ -106,7 +106,7 @@ public class Tags
public static final Tag<Item> GEMS_PRISMARINE = tag("gems/prismarine");
public static final Tag<Item> GEMS_QUARRTZ = tag("gems/quartz");
public static final Tag<Item> INGOTS = tag("ingots");
public static final Tag<Item> INGOTS_BRICK = tag("ingots/brrick");
public static final Tag<Item> INGOTS_BRICK = tag("ingots/brick");
public static final Tag<Item> INGOTS_GOLD = tag("ingots/gold");
public static final Tag<Item> INGOTS_IRON = tag("ingots/iron");
public static final Tag<Item> INGOTS_NETHER_BRICK = tag("ingots/nether_brick");

View File

@ -22,17 +22,22 @@ package net.minecraftforge.common.crafting;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntComparators;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.PacketBuffer;
public class CompoundIngredient extends Ingredient
{
@ -41,15 +46,11 @@ public class CompoundIngredient extends Ingredient
private IntList itemIds;
private final boolean isSimple;
protected CompoundIngredient(Collection<Ingredient> children)
protected CompoundIngredient(Stream<Ingredient> children)
{
super(0);
this.children = children;
boolean simple = true;
for (Ingredient child : children)
simple &= child.isSimple();
this.isSimple = simple;
super(Stream.of());
this.isSimple = children.allMatch(Ingredient::isSimple);
this.children = children.collect(Collectors.toList());
}
@Override
@ -84,16 +85,12 @@ public class CompoundIngredient extends Ingredient
}
@Override
public boolean apply(@Nullable ItemStack target)
public boolean test(@Nullable ItemStack target)
{
if (target == null)
return false;
for (Ingredient child : children)
if (child.apply(target))
return true;
return false;
return children.stream().anyMatch(c -> c.test(target));
}
@Override
@ -109,10 +106,33 @@ public class CompoundIngredient extends Ingredient
{
return isSimple;
}
@Nonnull
public Collection<Ingredient> getChildren()
{
return Collections.unmodifiableCollection(this.children);
}
public static class Serializer implements IIngredientSerializer<CompoundIngredient>
{
@Override
public CompoundIngredient parse(PacketBuffer buffer)
{
return new CompoundIngredient(Stream.generate(() -> Ingredient.func_199566_b(buffer)).limit(buffer.readVarInt()));
}
@Override
public CompoundIngredient parse(JsonObject json)
{
throw new JsonSyntaxException("CompountIngredient should not be directly referenced in json, just use an array of ingredients.");
}
@Override
public void write(PacketBuffer buffer, CompoundIngredient ingredient)
{
buffer.writeVarInt(ingredient.children.size());
ingredient.children.forEach(c -> c.func_199564_a(buffer));
}
}
}

View File

@ -19,31 +19,21 @@
package net.minecraftforge.common.crafting;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.client.util.RecipeBookClient;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import org.apache.commons.io.FilenameUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
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;
@ -51,92 +41,140 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
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.item.crafting.Ingredient.IItemList;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.nbt.NBTException;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.oredict.OreDictionary;
import net.minecraftforge.oredict.OreIngredient;
import net.minecraftforge.oredict.ShapedOreRecipe;
import net.minecraftforge.oredict.ShapelessOreRecipe;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.ForgeRegistry;
import net.minecraftforge.registries.GameData;
import net.minecraftforge.registries.RegistryManager;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
public class CraftingHelper {
private static final boolean DEBUG_LOAD_MINECRAFT = false;
public class CraftingHelper
{
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker CRAFTHELPER = MarkerManager.getMarker("CRAFTHELPER");
private static Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
private static Map<ResourceLocation, IConditionFactory> conditions = new HashMap<>();
private static Map<ResourceLocation, IIngredientFactory> ingredients = new HashMap<>();
private static Map<ResourceLocation, IRecipeFactory> recipes = new HashMap<>();
private static final Map<ResourceLocation, IConditionSerializer> conditions = new HashMap<>();
private static final BiMap<ResourceLocation, IIngredientSerializer<?>> ingredients = HashBiMap.create();
private static Map<ResourceLocation, IItemList> constants = new HashMap<>();
static {
init();
}
public static final IConditionSerializer CONDITION_MOD_LOADED = condition("mod_loaded", json -> {
String modid = JsonUtils.getString(json, "modid");
return () -> ModList.get().isLoaded(modid);
});
public static final IConditionSerializer CONDITION_ITEM_EXISTS = condition("item_exists", json -> {
String itemName = JsonUtils.getString(json, "item");
return () -> ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName));
});
public static final IConditionSerializer CONDITION_NOT = condition("not", json -> {
BooleanSupplier child = CraftingHelper.getCondition(JsonUtils.getJsonObject(json, "value"));
return () -> !child.getAsBoolean();
});
public static final IConditionSerializer CONDITION_OR = condition("or", 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()));
}
return () -> children.stream().anyMatch(BooleanSupplier::getAsBoolean);
});
public static final IConditionSerializer CONDITION_AND = condition("and", 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()));
}
return () -> children.stream().allMatch(c -> c.getAsBoolean());
});
public static final IConditionSerializer CONDITION_FALSE = condition("false", json -> () -> false);
public static void register(ResourceLocation key, IConditionFactory factory)
public static final IIngredientSerializer<IngredientNBT> INGREDIENT_NBT = register(new ResourceLocation("forge", "nbt"), new IngredientNBT.Serializer());
public static final IIngredientSerializer<CompoundIngredient> INGREDIENT_COMPOUND = register(new ResourceLocation("forge", "nbt"), new CompoundIngredient.Serializer());
public static final IIngredientSerializer<Ingredient> INGREDIENT_VANILLA = register(new ResourceLocation("minecraft", "item"), new IIngredientSerializer<Ingredient>() {
public Ingredient parse(PacketBuffer buffer) {
return Ingredient.func_209357_a(Stream.generate(() -> new Ingredient.SingleItemList(buffer.readItemStack())).limit(buffer.readVarInt()));
}
public Ingredient parse(JsonObject json) {
return Ingredient.func_209357_a(Stream.of(Ingredient.func_199803_a(json)));
}
public void write(PacketBuffer buffer, Ingredient ingredient) {
ItemStack[] items = ingredient.getMatchingStacks();
buffer.writeVarInt(items.length);
for (ItemStack stack : items)
buffer.writeItemStack(stack);
}
});
public static IConditionSerializer register(ResourceLocation key, IConditionSerializer serializer)
{
if (conditions.containsKey(key))
throw new IllegalStateException("Duplicate recipe condition factory: " + key);
conditions.put(key, factory);
throw new IllegalStateException("Duplicate recipe condition serializer: " + key);
conditions.put(key, serializer);
return serializer;
}
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)
public static <T extends Ingredient> IIngredientSerializer<T> register(ResourceLocation key, IIngredientSerializer<T> serializer)
{
if (ingredients.containsKey(key))
throw new IllegalStateException("Duplicate recipe ingredient factory: " + key);
ingredients.put(key, factory);
throw new IllegalStateException("Duplicate recipe ingredient serializer: " + key);
if (ingredients.containsValue(serializer))
throw new IllegalStateException("Duplicate recipe ingredient serializer: " + key + " " + serializer);
ingredients.put(key, serializer);
return serializer;
}
private static IConditionSerializer condition(String name, IConditionSerializer serializer) {
return register(new ResourceLocation("forge", name), serializer);
}
public static Ingredient getIngredient(Object obj)
public static <T extends Ingredient> void write(PacketBuffer buffer, T ingredient)
{
if (obj instanceof Ingredient)
return (Ingredient)obj;
else if (obj instanceof ItemStack)
return Ingredient.fromStacks(((ItemStack)obj).copy());
else if (obj instanceof Item)
return Ingredient.fromItem((Item)obj);
else if (obj instanceof Block)
return Ingredient.fromStacks(new ItemStack((Block)obj, 1, OreDictionary.WILDCARD_VALUE));
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;
@SuppressWarnings("unchecked") //I wonder if there is a better way generic wise...
IIngredientSerializer<T> serializer = (IIngredientSerializer<T>)ingredient.getSerializer();
ResourceLocation key = ingredients.inverse().get(serializer);
if (key == null)
throw new IllegalArgumentException("Tried to serialize unregistered Ingredient: " + ingredient + " " + serializer);
if (serializer != INGREDIENT_VANILLA)
{
buffer.writeVarInt(-1); //Marker to know there is a custom ingredient
buffer.writeResourceLocation(key);
}
serializer.write(buffer, ingredient);
}
@Nonnull
public static Ingredient getIngredient(JsonElement json, JsonContext context)
public static Ingredient getIngredient(ResourceLocation type, PacketBuffer buffer)
{
IIngredientSerializer<?> serializer = ingredients.get(type);
if (serializer == null)
throw new IllegalArgumentException("Can not deserialize unknown Ingredient type: " + type);
return serializer.parse(buffer);
}
public static Ingredient getIngredient(JsonElement json)
{
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())
{
@ -144,23 +182,16 @@ public class CraftingHelper {
List<Ingredient> vanilla = Lists.newArrayList();
json.getAsJsonArray().forEach((ele) ->
{
Ingredient ing = CraftingHelper.getIngredient(ele, context);
Ingredient ing = CraftingHelper.getIngredient(ele);
if (ing.getClass() == Ingredient.class)
{
//Vanilla, Due to how we read it splits each itemstack, so we pull out to re-merge later
if (ing.getClass() == Ingredient.class) //Vanilla, Due to how we read it splits each itemstack, so we pull out to re-merge later
vanilla.add(ing);
}
else
{
ingredients.add(ing);
}
});
if (!vanilla.isEmpty())
{
ingredients.add(Ingredient.merge(vanilla));
}
if (ingredients.size() == 0)
throw new JsonSyntaxException("Item array cannot be empty, at least one item must be defined");
@ -168,7 +199,7 @@ public class CraftingHelper {
if (ingredients.size() == 1)
return ingredients.get(0);
return new CompoundIngredient(ingredients);
return new CompoundIngredient(ingredients.stream());
}
if (!json.isJsonObject())
@ -176,42 +207,27 @@ public class CraftingHelper {
JsonObject obj = (JsonObject)json;
String type = context.appendModId(JsonUtils.getString(obj, "type", "minecraft:item"));
String type = 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)
IIngredientSerializer<?> serializer = ingredients.get(new ResourceLocation(type));
if (serializer == null)
throw new JsonSyntaxException("Unknown ingredient type: " + type);
return factory.parse(context, obj);
return serializer.parse(obj);
}
public static ItemStack getItemStack(JsonObject json, JsonContext context)
public static ItemStack getItemStack(JsonObject json, boolean readNBT)
{
String itemName = context.appendModId(JsonUtils.getString(json, "item"));
String itemName = 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"))
if (readNBT && json.has("nbt"))
{
// Lets hope this works? Needs test
try
@ -233,138 +249,19 @@ public class CraftingHelper {
tmp.setTag("tag", nbt);
tmp.setString("id", itemName);
tmp.setInteger("Count", JsonUtils.getInt(json, "count", 1));
tmp.setInteger("Damage", JsonUtils.getInt(json, "data", 0));
return new ItemStack(tmp);
return ItemStack.func_199557_a(tmp);
}
catch (NBTException e)
catch (CommandSyntaxException e)
{
throw new JsonSyntaxException("Invalid NBT Entry: " + e.toString());
}
}
return new ItemStack(item, JsonUtils.getInt(json, "count", 1), JsonUtils.getInt(json, "data", 0));
return new ItemStack(item, JsonUtils.getInt(json, "count", 1));
}
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();
itemMap.put(' ', Ingredient.EMPTY);
for (; idx < recipe.length; idx += 2)
{
Character chr = (Character)recipe[idx];
Object in = recipe[idx + 1];
Ingredient ing = CraftingHelper.getIngredient(in);
if (' ' == chr.charValue())
throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
if (ing != null)
{
itemMap.put(chr, ing);
}
else
{
String err = "Invalid shaped ore recipe: ";
for (Object tmp : recipe)
{
err += tmp + ", ";
}
throw new RuntimeException(err);
}
}
ret.input = NonNullList.withSize(ret.width * ret.height, Ingredient.EMPTY);
Set<Character> keys = Sets.newHashSet(itemMap.keySet());
keys.remove(' ');
int x = 0;
for (char chr : shape.toCharArray())
{
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);
}
if (!keys.isEmpty())
throw new IllegalArgumentException("Key defines symbols that aren't used in pattern: " + keys);
return ret;
}
public static boolean processConditions(JsonArray conditions, JsonContext context)
public static boolean processConditions(JsonArray conditions)
{
for (int x = 0; x < conditions.size(); x++)
{
@ -372,334 +269,82 @@ public class CraftingHelper {
throw new JsonSyntaxException("Conditions must be an array of JsonObjects");
JsonObject json = conditions.get(x).getAsJsonObject();
BooleanSupplier cond = CraftingHelper.getCondition(json, context);
BooleanSupplier cond = CraftingHelper.getCondition(json);
if (!cond.getAsBoolean())
return false;
}
return true;
}
public static BooleanSupplier getCondition(JsonObject json, JsonContext context)
public static BooleanSupplier getCondition(JsonObject json)
{
ResourceLocation type = new ResourceLocation(context.appendModId(JsonUtils.getString(json, "type")));
IConditionFactory factory = conditions.get(type);
if (factory == null)
ResourceLocation type = new ResourceLocation(JsonUtils.getString(json, "type"));
IConditionSerializer serrializer = conditions.get(type);
if (serrializer == null)
throw new JsonSyntaxException("Unknown condition type: " + type.toString());
return factory.parse(context, json);
return serrializer.parse(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);
@Nullable
public static IItemList getConstant(ResourceLocation key) {
return constants.get(key);
}
public static void reloadConstants(IResourceManager manager) {
Map<ResourceLocation, IItemList> ret = new HashMap<>();
for(ResourceLocation key : manager.func_199003_a("recipes", filename -> filename.equals("_constants.json")))
{
String path = key.getPath();
if (!path.equals("rrecipes/_constants.json")) //Top level only
continue;
//=======================================================
// INTERNAL
//=======================================================
private static void init()
{
conditions.clear();
ingredients.clear();
recipes.clear();
registerC("forge:mod_loaded", (context, json) -> {
String modid = JsonUtils.getString(json, "modid");
return () -> ModList.get().isLoaded(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)
try (IResource iresource = manager.func_199002_a(key))
{
if (!j.isJsonObject())
throw new JsonSyntaxException("Or condition values must be an array of JsonObjects");
children.add(CraftingHelper.getCondition(j.getAsJsonObject(), context));
}
return () -> children.stream().anyMatch(BooleanSupplier::getAsBoolean);
});
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;
});
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));
}
ingMap.put(' ', Ingredient.EMPTY);
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;
}
NonNullList<Ingredient> input = NonNullList.withSize(pattern[0].length() * pattern.length, Ingredient.EMPTY);
Set<Character> keys = Sets.newHashSet(ingMap.keySet());
keys.remove(' ');
int x = 0;
for (String line : pattern)
{
for (char chr : line.toCharArray())
JsonObject[] elements = JsonUtils.gsonDeserialize(GSON, IOUtils.toString(iresource.func_199027_b(), StandardCharsets.UTF_8), JsonObject[].class);
for (int x = 0; x < elements.length; x++)
{
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");
ItemStack itemstack = CraftingHelper.getItemStack(JsonUtils.getJsonObject(json, "result"), context);
return new ShapelessRecipes(group, itemstack, ings);
});
registerR("forge:ore_shaped", ShapedOreRecipe::factory);
registerR("forge:ore_shapeless", ShapelessOreRecipe::factory);
registerI("minecraft:item", (context, json) -> Ingredient.fromStacks(CraftingHelper.getItemStackBasic(json, context)));
registerI("minecraft:empty", (context, json) -> Ingredient.EMPTY);
registerI("minecraft:item_nbt", (context, json) -> new IngredientNBT(CraftingHelper.getItemStack(json, context)));
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);
}
private 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() + "]");
register(key, getClassInstance(clsName, IIngredientFactory.class));
}
}
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() + "]");
register(key, getClassInstance(clsName, IRecipeFactory.class));
}
}
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));
}
}
}
@SuppressWarnings("unchecked")
private static <T> T getClassInstance(String clsName, Class<T> expected)
{
try
{
Class<?> cls = Class.forName(clsName, true, Thread.currentThread().getContextClassLoader());
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);
}
}
public static void loadRecipes(boolean revertFrozen)
{
//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();
ForgeRegistry<IRecipe> reg = (ForgeRegistry<IRecipe>)ForgeRegistries.RECIPES;
//reg.unfreeze();
if (DEBUG_LOAD_MINECRAFT)
reg.clear();
else if (revertFrozen)
GameData.revert(RegistryManager.FROZEN, GameData.RECIPES, false);
ModList.get().forEachModFile(CraftingHelper::loadFactories);
ModList.get().forEachModFile(CraftingHelper::loadRecipes);
GameData.fireRegistryEvents(rl -> rl.equals(GameData.RECIPES));
//reg.freeze();
DistExecutor.runWhenOn(Dist.CLIENT, ()-> RecipeBookClient::rebuildTable);
}
private static void loadFactories(final ModFile modFile)
{
modFile.getModInfos().forEach(modInfo-> {
final String modId = modInfo.getModId();
JsonContext ctx = new JsonContext(modId);
final Path fPath = modFile.getLocator().findPath(modFile, "assets", modId, "recipes","_factories.json");
if (fPath != null && Files.exists(fPath))
{
try (final BufferedReader reader = Files.newBufferedReader(fPath))
{
JsonObject json = JsonUtils.fromJson(GSON, reader, JsonObject.class);
loadFactories(json, ctx);
}
catch (final IOException e)
{
LOGGER.error(CRAFTHELPER,"Encountered error reading recipe factories for {}", modId, e);
}
}
});
}
private static boolean loadRecipes(final ModFile modFile)
{
final AtomicBoolean errored = new AtomicBoolean(false);
modFile.getModInfos().forEach(modInfo-> {
final String modId = modInfo.getModId();
JsonContext ctx = new JsonContext(modId);
final Path root = modFile.getLocator().findPath(modFile, "assets", modId, "recipes");
if (!Files.exists(root)) return;
final Path constants = modFile.getLocator().findPath(modFile, "assets", modId, "recipes", "_constants.json");
if (Files.exists(constants))
{
try (BufferedReader reader = Files.newBufferedReader(constants))
{
JsonObject[] json = JsonUtils.fromJson(GSON, reader, JsonObject[].class);
ctx.loadConstants(json);
}
catch (final IOException e)
{
LOGGER.error(CRAFTHELPER, "Error loading _constants.json: ", e);
errored.set(true);
}
}
try
{
Files.walk(root).
filter(p -> p.getFileName().toString().startsWith("_") || Objects.equals(FilenameUtils.getExtension(p.getFileName().toString()), "json")).
forEach(p -> {
final String relative = root.relativize(p).toString();
final String name = FilenameUtils.removeExtension(relative).replaceAll("\\\\", "/");
final ResourceLocation key = new ResourceLocation(modId, name);
try (BufferedReader reader = Files.newBufferedReader(constants))
JsonObject json = elements[x];
if (json == null || json.size() == 0)
LOGGER.error(CRAFTHELPER, "Couldn't load constant #{} from {} as it's null or empty", x, key);
else if (json.has("conditions") && !processConditions(JsonUtils.getJsonArray(json, "conditions")))
LOGGER.info(CRAFTHELPER, "Skipping loading constant #{} from {} as it's conditions were not met", x, key);
else if (!json.has("name"))
LOGGER.error(CRAFTHELPER, "Couldn't load constant #{} from {} as it's missing `name`", x, key);
else if (json.has("items"))
{
List<ItemStack> items = new ArrayList<>();
for (JsonElement item : JsonUtils.getJsonArray(json, "items"))
{
if (item.isJsonObject())
items.add(getItemStack(item.getAsJsonObject(), true));
else
{
JsonObject json = JsonUtils.fromJson(GSON, reader, JsonObject.class);
if (json.has("conditions") && !CraftingHelper.processConditions(JsonUtils.getJsonArray(json, "conditions"), ctx))
return;
IRecipe recipe = CraftingHelper.getRecipe(json, ctx);
ForgeRegistries.RECIPES.register(recipe.setRegistryName(key));
}
catch (final JsonParseException e)
{
LOGGER.error(CRAFTHELPER, "Parsing error loading recipe {}", key, e);
errored.set(true);
}
catch (final IOException e)
{
LOGGER.error(CRAFTHELPER, "Couldn't read recipe {} from {}", key, p, e);
errored.set(true);
LOGGER.error(CRAFTHELPER, "Couldn't load constant #{} from {} as it's `items` entry is not a object", x, key);
items.clear();
break;
}
}
if (!items.isEmpty())
constants.put(new ResourceLocation(JsonUtils.getString(json, "name")), new StackList(items));
}
else if (json.has("tag"))
constants.put(new ResourceLocation(JsonUtils.getString(json, "name")), Ingredient.func_199803_a(json));
else if (json.has("item"))
constants.put(new ResourceLocation(JsonUtils.getString(json, "name")), new StackList(Lists.newArrayList(getItemStack(JsonUtils.getJsonObject(json, "item"), true))));
else
LOGGER.error(CRAFTHELPER, "Couldn't load constant #{} from {} as it's missing `item` or `items` element", x, key);
}
});
}
catch (final IOException e)
catch (IllegalArgumentException | JsonParseException e)
{
LOGGER.error(CRAFTHELPER, "Error occurred walking file tree", e);
errored.set(true);
LOGGER.error(CRAFTHELPER, "Parsing error loading constants {}", key, e);
}
});
return errored.get();
catch (IOException e)
{
LOGGER.error(CRAFTHELPER, "Couldn't read constants from {}", key, e);
}
}
constants = ret;
}
}

View File

@ -23,6 +23,8 @@ import java.util.function.BooleanSupplier;
import com.google.gson.JsonObject;
public interface IConditionFactory {
BooleanSupplier parse(JsonContext context, JsonObject json);
@FunctionalInterface
public interface IConditionSerializer
{
BooleanSupplier parse(JsonObject json);
}

View File

@ -19,14 +19,16 @@
package net.minecraftforge.common.crafting;
import javax.annotation.Nonnull;
import com.google.gson.JsonObject;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.PacketBuffer;
public interface IIngredientFactory
public interface IIngredientSerializer<T extends Ingredient>
{
@Nonnull //If you would return null throw JsonSyntaxException to explain why
Ingredient parse(JsonContext context, JsonObject json);
T parse(PacketBuffer buffer);
T parse(JsonObject json);
void write(PacketBuffer buffer, T ingredient);
}

View File

@ -1,28 +0,0 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* 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 com.google.gson.JsonObject;
import net.minecraft.item.crafting.IRecipe;
public interface IRecipeFactory {
IRecipe parse(JsonContext context, JsonObject json);
}

View File

@ -19,22 +19,27 @@
package net.minecraftforge.common.crafting;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.gson.JsonObject;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.PacketBuffer;
public class IngredientNBT extends Ingredient
{
private final ItemStack stack;
protected IngredientNBT(ItemStack stack)
{
super(stack);
super(Stream.of(new Ingredient.SingleItemList(stack)));
this.stack = stack;
}
@Override
public boolean apply(@Nullable ItemStack input)
public boolean test(@Nullable ItemStack input)
{
if (input == null)
return false;
@ -47,4 +52,22 @@ public class IngredientNBT extends Ingredient
{
return false;
}
public static class Serializer implements IIngredientSerializer<IngredientNBT>
{
@Override
public IngredientNBT parse(PacketBuffer buffer) {
return new IngredientNBT(buffer.readItemStack());
}
@Override
public IngredientNBT parse(JsonObject json) {
return new IngredientNBT(CraftingHelper.getItemStack(json, true));
}
@Override
public void write(PacketBuffer buffer, IngredientNBT ingredient) {
buffer.writeItemStack(ingredient.stack);
}
}
}

View File

@ -1,73 +0,0 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* 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.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.JsonUtils;
public class JsonContext
{
private String modId;
private Map<String, Ingredient> constants = Maps.newHashMap();
public JsonContext(String modId)
{
this.modId = modId;
}
public String getModId()
{
return this.modId;
}
public String appendModId(String data)
{
if (data.indexOf(':') == -1)
return modId + ":" + data;
return data;
}
@Nullable
public Ingredient getConstant(String name)
{
return constants.get(name);
}
void loadConstants(JsonObject[] jsons)
{
for (JsonObject json : jsons)
{
if (json.has("conditions") && !CraftingHelper.processConditions(json.getAsJsonArray("conditions"), this))
continue;
if (!json.has("ingredient"))
throw new JsonSyntaxException("Constant entry must contain 'ingredient' value");
constants.put(JsonUtils.getString(json, "name"), CraftingHelper.getIngredient(json.get("ingredient"), this));
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* 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.util.Collection;
import java.util.Collections;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient.IItemList;
public class StackList implements IItemList
{
private Collection<ItemStack> items;
public StackList(Collection<ItemStack> items)
{
this.items = Collections.unmodifiableCollection(items);
}
@Override
public Collection<ItemStack> func_199799_a()
{
return items;
}
@Override
public JsonObject func_200303_b()
{
if (items.size() == 1)
return toJson(items.iterator().next());
JsonObject ret = new JsonObject();
JsonArray array = new JsonArray();
items.forEach(stack -> array.add(toJson(stack)));
ret.add("items", array);
return ret;
}
private JsonObject toJson(ItemStack stack)
{
JsonObject ret = new JsonObject();
ret.addProperty("item", stack.getItem().getRegistryName().toString());
if (stack.getCount() != 1)
ret.addProperty("count", stack.getCount());
if (stack.getTagCompound() != null)
ret.addProperty("nbt", stack.getTagCompound().toString()); //TODO: Better serialization?
return ret;
}
}

View File

@ -0,0 +1,7 @@
// Auto generated package-info by MCP
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.common.crafting;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -345,7 +345,15 @@ protected net.minecraft.world.Teleporter field_85191_c # destinationCoordinateCa
public net.minecraft.util.ResourceLocation func_177516_a(Ljava/lang/String;)[Ljava/lang/String; # splitObjectName
# Ingredient
protected net.minecraft.item.crafting.Ingredient <init>([Lnet/minecraft/item/ItemStack;)V # Ingredient
public-f net.minecraft.item.crafting.Ingredient
protected net.minecraft.item.crafting.Ingredient <init>(Ljava/util/stream/Stream;)V
public net.minecraft.item.crafting.Ingredient func_209357_a(Ljava/util/stream/Stream;)Lnet/minecraft/item/crafting/Ingredient;
public+f net.minecraft.item.crafting.Ingredient func_199564_a(Lnet/minecraft/network/PacketBuffer;)V
public net.minecraft.item.crafting.Ingredient$IItemList
public net.minecraft.item.crafting.Ingredient$SingleItemList
public net.minecraft.item.crafting.Ingredient$SingleItemList <init>(Lnet/minecraft/item/ItemStack;)V
public net.minecraft.item.crafting.Ingredient$TagList
public net.minecraft.item.crafting.Ingredient$TagList <init>(Lnet/minecraft/tags/Tag;)V
# Crafting
public net.minecraft.client.Minecraft func_193986_ar()V # populateSearchTreeManager