Create custom shapeless recipe matching system. Should solve #4516 damageable items in shapeless recipes.

This commit is contained in:
LexManos 2017-11-22 13:26:02 -08:00
parent ad16f15365
commit 89db87dbfc
11 changed files with 368 additions and 19 deletions

View file

@ -1,6 +1,11 @@
--- ../src-base/minecraft/net/minecraft/item/crafting/Ingredient.java
+++ ../src-work/minecraft/net/minecraft/item/crafting/Ingredient.java
@@ -13,6 +13,8 @@
@@ -8,11 +8,11 @@
import net.minecraft.client.util.RecipeItemHelper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
-import net.minecraftforge.fml.relauncher.Side;
-import net.minecraftforge.fml.relauncher.SideOnly;
public class Ingredient implements Predicate<ItemStack>
{
@ -9,12 +14,13 @@
public static final Ingredient field_193370_a = new Ingredient(new ItemStack[0])
{
public boolean apply(@Nullable ItemStack p_apply_1_)
@@ -21,17 +23,34 @@
@@ -21,17 +21,41 @@
}
};
private final ItemStack[] field_193371_b;
+ private final ItemStack[] matchingStacksExploded;
private IntList field_194140_c;
+ private final boolean isSimple;
+ protected Ingredient(int size)
+ {
@ -23,6 +29,7 @@
+
protected Ingredient(ItemStack... p_i47503_1_)
{
+ boolean simple = true;
this.field_193371_b = p_i47503_1_;
+ net.minecraft.util.NonNullList<ItemStack> lst = net.minecraft.util.NonNullList.func_191196_a();
+ for (ItemStack s : p_i47503_1_)
@ -30,11 +37,16 @@
+ if (s.func_190926_b())
+ continue;
+ if (s.func_77960_j() == net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE)
+ {
+ if (s.func_77973_b().func_77645_m())
+ simple = false;
+ s.func_77973_b().func_150895_a(net.minecraft.creativetab.CreativeTabs.field_78027_g, lst);
+ }
+ else
+ lst.add(s);
+ }
+ this.matchingStacksExploded = lst.toArray(new ItemStack[lst.size()]);
+ this.isSimple = simple && this.matchingStacksExploded.length > 0;
+ Ingredient.INSTANCES.add(this);
}
@ -46,7 +58,7 @@
}
public boolean apply(@Nullable ItemStack p_apply_1_)
@@ -63,9 +82,9 @@
@@ -63,9 +87,9 @@
{
if (this.field_194140_c == null)
{
@ -58,7 +70,7 @@
{
this.field_194140_c.add(RecipeItemHelper.func_194113_b(itemstack));
}
@@ -76,6 +95,18 @@
@@ -76,6 +100,18 @@
return this.field_194140_c;
}
@ -77,7 +89,7 @@
public static Ingredient func_193367_a(Item p_193367_0_)
{
return func_193369_a(new ItemStack(p_193367_0_, 1, 32767));
@@ -108,4 +139,17 @@
@@ -108,4 +144,22 @@
return field_193370_a;
}
@ -93,5 +105,10 @@
+ lst.add(stack);
+ }
+ return new Ingredient(lst.toArray(new ItemStack[lst.size()]));
+ }
+
+ public boolean isSimple()
+ {
+ return isSimple || this == field_193370_a;
+ }
}

View file

@ -1,6 +1,6 @@
--- ../src-base/minecraft/net/minecraft/item/crafting/ShapelessRecipes.java
+++ ../src-work/minecraft/net/minecraft/item/crafting/ShapelessRecipes.java
@@ -10,10 +10,8 @@
@@ -10,23 +10,25 @@
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.world.World;
@ -12,15 +12,25 @@
{
private final ItemStack field_77580_a;
public final NonNullList<Ingredient> field_77579_b;
@@ -26,7 +24,6 @@
private final String field_194138_c;
+ private final boolean isSimple;
public ShapelessRecipes(String p_i47500_1_, ItemStack p_i47500_2_, NonNullList<Ingredient> p_i47500_3_)
{
this.field_194138_c = p_i47500_1_;
this.field_77580_a = p_i47500_2_;
this.field_77579_b = p_i47500_3_;
+ boolean simple = true;
+ for (Ingredient i : p_i47500_3_)
+ simple &= i.isSimple();
+ this.isSimple = simple;
}
- @SideOnly(Side.CLIENT)
public String func_193358_e()
{
return this.field_194138_c;
@@ -50,10 +47,7 @@
@@ -50,10 +52,7 @@
{
ItemStack itemstack = p_179532_1_.func_70301_a(i);
@ -32,17 +42,18 @@
}
return nonnulllist;
@@ -61,7 +55,8 @@
@@ -61,7 +60,9 @@
public boolean func_77569_a(InventoryCrafting p_77569_1_, World p_77569_2_)
{
- List<Ingredient> list = Lists.newArrayList(this.field_77579_b);
+ int ingredientCount = 0;
+ net.minecraft.client.util.RecipeItemHelper recipeItemHelper = new net.minecraft.client.util.RecipeItemHelper();
+ List<ItemStack> inputs = Lists.newArrayList();
for (int i = 0; i < p_77569_1_.func_174923_h(); ++i)
{
@@ -71,27 +66,13 @@
@@ -71,27 +72,22 @@
if (!itemstack.func_190926_b())
{
@ -63,17 +74,26 @@
- return false;
- }
+ ++ingredientCount;
+ recipeItemHelper.func_194112_a(itemstack);
+ if (this.isSimple)
+ recipeItemHelper.func_194112_a(itemstack);
+ else
+ inputs.add(itemstack);
}
}
}
- return list.isEmpty();
+ return ingredientCount == this.field_77579_b.size() && recipeItemHelper.func_194116_a(this, null);
+ if (ingredientCount != this.field_77579_b.size())
+ return false;
+
+ if (this.isSimple)
+ return recipeItemHelper.func_194116_a(this, null);
+
+ return net.minecraftforge.common.util.RecipeMatcher.findMatches(inputs, this.field_77579_b) != null;
}
public ItemStack func_77572_b(InventoryCrafting p_77572_1_)
@@ -136,7 +117,6 @@
@@ -136,7 +132,6 @@
return nonnulllist;
}

View file

@ -20,11 +20,17 @@ public class CompoundIngredient extends Ingredient
private Collection<Ingredient> children;
private ItemStack[] stacks;
private IntList itemIds;
private final boolean isSimple;
protected CompoundIngredient(Collection<Ingredient> children)
{
super(0);
this.children = children;
boolean simple = true;
for (Ingredient child : children)
simple &= child.isSimple();
this.isSimple = simple;
}
@Override
@ -78,4 +84,10 @@ public class CompoundIngredient extends Ingredient
this.stacks = null;
//Shouldn't need to invalidate children as this is only called form invalidateAll..
}
@Override
public boolean isSimple()
{
return isSimple;
}
}

View file

@ -40,4 +40,10 @@ public class IngredientNBT extends Ingredient
//Can't use areItemStacksEqualUsingNBTShareTag because it compares stack size as well
return this.stack.getItem() == input.getItem() && this.stack.getItemDamage() == input.getItemDamage() && ItemStack.areItemStackShareTagsEqual(this.stack, input);
}
@Override
public boolean isSimple()
{
return false;
}
}

View file

@ -0,0 +1,162 @@
/*
* 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.util;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import com.google.common.base.Predicate;
public class RecipeMatcher
{
/**
* Attempts to match inputs to the specified tests. In the best way that all inputs are used by one test.
* Will return null in any of these cases:
* input/test lengths don't match. This is only for matching paired outputs.
* any input doesn't match a test
* any test doesn't match a input
* If we are unable to determine a proper pair
*
* @return An array mapping inputs to tests. ret[x] = y means input[x] = test[y]
*/
public static <T> int[] findMatches(List<T> inputs, List<? extends Predicate<T>> tests)
{
int elements = inputs.size();
if (elements != tests.size())
return null; // There will not be a 1:1 mapping of inputs -> tests
int[] ret = new int[elements];
for (int x = 0; x < elements; x++)
ret[x] = -1;
// [UnusedInputs] [UnusedIngredients] [IngredientMatchMask]...
BitSet data = new BitSet((elements + 2) * elements);
for (int x = 0; x < elements; x++)
{
int matched = 0;
int offset = (x + 2) * elements;
Predicate<T> test = tests.get(x);
for (int y = 0; y < elements; y++)
{
if (data.get(y))
continue;
if (test.apply(inputs.get(y)))
{
data.set(offset + y);
matched++;
}
}
if (matched == 0)
return null; //We have an test that matched non of the inputs
if (matched == 1)
{
if (!claim(ret, data, x, elements))
return null; //We failed to claim this index, which means it caused something else to go to 0 matches, which makes the whole thing fail
}
}
if (data.nextClearBit(0) >= elements) //All items have been used, which means all tests have a match!
return ret;
// We should be in a state where multiple tests are satified by multiple inputs. So we need to try a branching recursive test.
// However for performance reasons, we should probably make that check a sub-set of the entire graph.
if (backtrack(data, ret, 0, elements))
return ret;
return null; //Backtrack failed, no matches, we cry and go home now :(
}
// This is bad... need to think of a better cascade, recursion instead of stack?
private static boolean claim(int[] ret, BitSet data, int claimed, int elements)
{
Queue<Integer> pending = new LinkedList<Integer>();
pending.add(claimed);
while (pending.peek() != null)
{
int test = pending.poll();
int offset = (test + 2) * elements;
int used = data.nextSetBit(offset) - offset;
if (used >= elements || used < 0)
throw new IllegalStateException("What? We matched something, but it wasn't set in the range of this test! Test: " + test + " Used: " + used);
data.set(used);
data.set(elements + test);
ret[used] = test;
for (int x = 0; x < elements; x++)
{
offset = (x + 2) * elements;
if (data.get(offset + used) && !data.get(elements + x))
{
data.clear(offset + used);
int count = 0;
for (int y = offset; y < offset + elements; y++)
if (data.get(y))
count++;
if (count == 0)
return false; //Claiming this caused another test to lose its last match..
if (count == 1)
pending.add(x);
}
}
}
return true;
}
//We use recursion here, why? Because I feel like it. Also because we should only ever be working in data sets < 9
private static boolean backtrack(BitSet data, int[] ret, int start, int elements)
{
int test = data.nextClearBit(elements + start) - elements;
if (test >= elements)
return true; //Could not find the next unused test.
if (test < 0)
throw new IllegalStateException("This should never happen, negative test in backtrack!");
int offset = (test + 2) * elements;
for (int x = 0; x < elements; x++)
{
if (!data.get(offset + x) || data.get(x))
continue;
data.set(x);
if (backtrack(data, ret, test + 1, elements))
{
ret[x] = test;
return true;
}
data.clear(x);
}
return false;
}
}

View file

@ -114,4 +114,10 @@ public class OreIngredient extends Ingredient
this.itemIds = null;
this.array = null;
}
@Override
public boolean isSimple()
{
return true;
}
}

View file

@ -33,6 +33,7 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.JsonContext;
import net.minecraftforge.common.util.RecipeMatcher;
import net.minecraftforge.registries.IForgeRegistryEntry;
import javax.annotation.Nonnull;
@ -48,6 +49,7 @@ public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl<IRecipe> implem
protected ItemStack output = ItemStack.EMPTY;
protected NonNullList<Ingredient> input = NonNullList.create();
protected ResourceLocation group;
protected boolean isSimple = true;
public ShapelessOreRecipe(ResourceLocation group, Block result, Object... recipe){ this(group, new ItemStack(result), recipe); }
public ShapelessOreRecipe(ResourceLocation group, Item result, Object... recipe){ this(group, new ItemStack(result), recipe); }
@ -56,6 +58,8 @@ public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl<IRecipe> implem
this.group = group;
output = result.copy();
this.input = input;
for (Ingredient i : input)
this.isSimple &= i.isSimple();
}
public ShapelessOreRecipe(ResourceLocation group, @Nonnull ItemStack result, Object... recipe)
{
@ -67,6 +71,7 @@ public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl<IRecipe> implem
if (ing != null)
{
input.add(ing);
this.isSimple &= ing.isSimple();
}
else
{
@ -94,6 +99,7 @@ public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl<IRecipe> implem
{
int ingredientCount = 0;
RecipeItemHelper recipeItemHelper = new RecipeItemHelper();
List<ItemStack> items = Lists.newArrayList();
for (int i = 0; i < inv.getSizeInventory(); ++i)
{
@ -101,11 +107,20 @@ public class ShapelessOreRecipe extends IForgeRegistryEntry.Impl<IRecipe> implem
if (!itemstack.isEmpty())
{
++ingredientCount;
recipeItemHelper.accountStack(itemstack);
if (this.isSimple)
recipeItemHelper.accountStack(itemstack);
else
items.add(itemstack);
}
}
return ingredientCount == this.input.size() && recipeItemHelper.canCraft(this, null);
if (ingredientCount != this.input.size())
return false;
if (this.isSimple)
return recipeItemHelper.canCraft(this, null);
return RecipeMatcher.findMatches(items, this.input) != null;
}
@Override

View file

@ -1,44 +1,108 @@
package net.minecraftforge.debug;
import java.util.Random;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.ShapedRecipes;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.debug.OnItemUseFirstTest.CommonProxy;
import net.minecraftforge.debug.OnItemUseFirstTest.ItemTest;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.oredict.ShapedOreRecipe;
@Mod(modid = "recipetest", name = "Recipe test mod", version = "1.0", acceptableRemoteVersions = "*")
@Mod(modid = RecipeTestMod.MODID, name = "Recipe test mod", version = "1.0", acceptableRemoteVersions = "*")
public class RecipeTestMod
{
public static final String MODID = "recipetest";
private static final boolean ENABLED = true;
@SidedProxy
public static CommonProxy proxy = null;
@Mod.EventHandler
public void preinit(FMLPreInitializationEvent event)
{
MinecraftForge.EVENT_BUS.register(this);
if (ENABLED)
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void registerRecipes(RegistryEvent.Register<IRecipe> event)
{
ResourceLocation location1 = new ResourceLocation("recipetest", "dirt");
ResourceLocation location1 = new ResourceLocation(MODID, "dirt");
ShapedOreRecipe recipe1 = new ShapedOreRecipe(location1, new ItemStack(Blocks.DIAMOND_BLOCK), "DDD", 'D', new ItemStack(Blocks.DIRT));
recipe1.setRegistryName(location1);
event.getRegistry().register(recipe1);
if (FMLLaunchHandler.side() == Side.SERVER)
{
ResourceLocation location2 = new ResourceLocation("recipetest", "stone");
ResourceLocation location2 = new ResourceLocation(MODID, "stone");
CraftingHelper.ShapedPrimer primer1 = CraftingHelper.parseShaped("SSS", 'S', new ItemStack(Blocks.IRON_BLOCK));
ShapedRecipes recipe2 = new ShapedRecipes(location2.getResourcePath(), primer1.width, primer1.height, primer1.input, new ItemStack(Blocks.GOLD_BLOCK));
recipe2.setRegistryName(location2);
event.getRegistry().register(recipe2);
}
}
@SubscribeEvent
public void registerItems(RegistryEvent.Register<Item> event)
{
proxy.registerItem(event);
}
public static abstract class CommonProxy
{
protected Item TOOL;
public void registerItem(RegistryEvent.Register<Item> event)
{
TOOL = new Item()
{
Random RAND = new Random();
@Override
public ItemStack getContainerItem(ItemStack in)
{
ItemStack ret = in.copy();
ret.attemptDamageItem(1, RAND, null);
return ret;
}
@Override
public boolean hasContainerItem()
{
return true;
}
}.setRegistryName(MODID, "tool").setMaxDamage(10).setCreativeTab(CreativeTabs.MISC).setUnlocalizedName("recipetest.tool").setMaxStackSize(1);
event.getRegistry().register(TOOL);
}
}
public static final class ServerProxy extends CommonProxy
{
}
public static final class ClientProxy extends CommonProxy
{
@Override
public void registerItem(RegistryEvent.Register<Item> event)
{
super.registerItem(event);
ModelLoader.setCustomModelResourceLocation(TOOL, 0, new ModelResourceLocation("minecraft:stick#inventory"));
}
}
}

View file

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{
"item": "minecraft:bow",
"data": 32767
}
],
"result": {
"item": "minecraft:wool",
"data": 9
}
}

View file

@ -0,0 +1,21 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{
"item": "tool",
"data": 32767
},
{
"item": "minecraft:stone",
"data": 32767
},
{
"item": "minecraft:stone",
"data": 0
}
],
"result": {
"item": "minecraft:wool",
"data": 0
}
}

View file

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{
"item": "tool",
"data": 32767
}
],
"result": {
"item": "minecraft:wool",
"data": 9
}
}