diff --git a/build.gradle b/build.gradle index 578d3ed..a1a0855 100644 --- a/build.gradle +++ b/build.gradle @@ -83,10 +83,13 @@ dependencies { compile 'CraftTweaker2:CraftTweaker2-MC1120-Main:1.12-4.1.20.673' compile 'curse.maven:had-enough-items-557549:3766122' - deobfCompile 'curse.maven:gregtech-ce-unofficial-557242:3805253' + //deobfCompile 'curse.maven:gregtech-ce-unofficial-557242:3805253' deobfCompile 'curse.maven:codechicken-lib-1-8-242818:2779848' deobfCompile 'curse.maven:the-one-probe-245211:2667280' + deobfCompile 'curse.maven:ctm-267602:2915363' compile (files("etc/modularui-1.0.jar")) + + provided (files("libs/gregtech-1.12.2-2.3.4-beta.jar")) } sourceSets { diff --git a/libs/gregtech-1.12.2-2.3.4-beta.jar b/libs/gregtech-1.12.2-2.3.4-beta.jar new file mode 100644 index 0000000..f3232c1 Binary files /dev/null and b/libs/gregtech-1.12.2-2.3.4-beta.jar differ diff --git a/src/main/java/clayium/api/metatileentity/ClayWorkableTieredMetaTileEntity.java b/src/main/java/clayium/api/metatileentity/ClayWorkableTieredMetaTileEntity.java index 13baa25..fe36e4b 100644 --- a/src/main/java/clayium/api/metatileentity/ClayWorkableTieredMetaTileEntity.java +++ b/src/main/java/clayium/api/metatileentity/ClayWorkableTieredMetaTileEntity.java @@ -120,69 +120,17 @@ protected FluidTankList createExportFluidHandler() { protected boolean canInputFluid(FluidStack inputFluid) { ClayRecipeMap recipeMap = workable.getRecipeMap(); - if (recipeMap.canInputFluidForce(inputFluid.getFluid())) { - return true; // RecipeMap forces input - } - Collection inputCapableRecipes = recipeMap.getRecipesForFluid(inputFluid); - if (inputCapableRecipes.isEmpty()) { - // Fluid cannot be inputted anyway, short-circuit and inform that the fluid cannot be inputted - return false; - } - boolean hasEmpty = false; - Collection fluids = null; - for (IFluidTank fluidTank : importFluids) { - FluidStack fluidInTank = fluidTank.getFluid(); - if (fluidInTank != null) { - if (inputFluid.isFluidEqual(fluidInTank)) { - // Both fluids are equal, short-circuit and inform that the fluid can be inputted + List fluidInputs = new ArrayList<>(); + for (IFluidTank fluidTank : this.importFluids.getFluidTanks()) { + FluidStack fluidStack = fluidTank.getFluid(); + if (fluidStack != null && fluidStack.amount > 0){ + if (fluidStack.isFluidEqual(inputFluid)) { return true; - } else { - if (fluids == null) { - // Avoid object allocation + array expansion as this is a hotspot - if (fluidKeyCache == null) { - fluids = new ObjectArraySet<>(new FluidKey[] { new FluidKey(fluidInTank) }); - } else { - fluidKeyCache.clear(); - fluids = fluidKeyCache; - } - } else { - fluids.add(new FluidKey(fluidInTank)); - } - } - } else { - hasEmpty = true; - } - } - if (!hasEmpty) { - // No empty slots to fill input in, inform that the fluid cannot be inputted - return false; - } - if (fluids == null) { - // There are empty slots to fill, and there are no other fluids, inform that the fluid can be inputted - return true; - } - for (FluidKey fluidKey : fluids) { - Collection iter = recipeMap.getRecipesForFluid(fluidKey); - Collection check; - // Iterate with the smaller collection as base - if (iter.size() > inputCapableRecipes.size()) { - check = iter; - iter = inputCapableRecipes; - } else { - check = inputCapableRecipes; - } - for (ClayRecipe it : iter) { - for (ClayRecipe ch : check) { - // Check identity equality, fast-track instead of doing Recipe#equals' tedious checks - if (it == ch) { - // Recipe exists in both collections, inform that the fluid can be inputted - return true; - } } + fluidInputs.add(fluidStack); } } - // Ultimatum - return false; + return recipeMap != null && recipeMap.acceptsFluid(fluidInputs, inputFluid); } @Override diff --git a/src/main/java/clayium/api/recipes/ClayRecipe.java b/src/main/java/clayium/api/recipes/ClayRecipe.java index 42f3967..782d01a 100644 --- a/src/main/java/clayium/api/recipes/ClayRecipe.java +++ b/src/main/java/clayium/api/recipes/ClayRecipe.java @@ -4,6 +4,8 @@ import gregtech.api.GTValues; import gregtech.api.capability.IMultipleTankHandler; import gregtech.api.recipes.*; +import gregtech.api.recipes.ingredients.GTRecipeInput; +import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage; import gregtech.api.recipes.recipeproperties.RecipeProperty; import gregtech.api.recipes.recipeproperties.RecipePropertyStorage; import gregtech.api.util.GTUtility; @@ -34,8 +36,8 @@ public class ClayRecipe extends Recipe { private final int tier; - public ClayRecipe(List inputs, List outputs, List chancedOutputs, List fluidInputs, List fluidOutputs, int duration, int EUt, boolean hidden, boolean isCTRecipe, int tier) { - super(inputs, outputs, chancedOutputs, fluidInputs, fluidOutputs, duration, EUt, hidden, isCTRecipe); + public ClayRecipe(List inputs, List outputs, List chancedOutputs, List fluidInputs, List fluidOutputs, int duration, int EUt, boolean hidden, boolean isCTRecipe, IRecipePropertyStorage recipePropertyStorage, int tier) { + super(inputs, outputs, chancedOutputs, fluidInputs, fluidOutputs, duration, EUt, hidden, isCTRecipe, recipePropertyStorage); this.tier = tier; } @@ -63,17 +65,8 @@ public List getResultItemOutputs(int tier, ClayRecipeMap recipeMap } public ClayRecipe copy() { - ClayRecipe newRecipe = new ClayRecipe(getInputs(), getOutputs(), getChancedOutputs(), getFluidInputs(), getFluidOutputs(), getDuration(), getEUt(), isHidden(), getIsCTRecipe(), getTier()); - if (getRecipePropertyStorage().getSize() > 0) { - Iterator var2 = this.getRecipePropertyStorage().getRecipeProperties().iterator(); - - while(var2.hasNext()) { - Map.Entry, Object> property = (Map.Entry)var2.next(); - newRecipe.setProperty((RecipeProperty)property.getKey(), property.getValue()); - } - } - - return newRecipe; + return new ClayRecipe(getInputs(), getOutputs(), getChancedOutputs(), getFluidInputs(), + getFluidOutputs(), getDuration(), getEUt(), isHidden(), getIsCTRecipe(), getRecipePropertyStorage(), getTier()); } public ClayRecipe trimRecipeOutputs(ClayRecipe currentRecipe, ClayRecipeMap recipeMap, int itemTrimLimit, int fluidTrimLimit) { diff --git a/src/main/java/clayium/api/recipes/ClayRecipeBuilder.java b/src/main/java/clayium/api/recipes/ClayRecipeBuilder.java index db7cb22..3ed0f03 100644 --- a/src/main/java/clayium/api/recipes/ClayRecipeBuilder.java +++ b/src/main/java/clayium/api/recipes/ClayRecipeBuilder.java @@ -1,11 +1,16 @@ package clayium.api.recipes; - import crafttweaker.CraftTweakerAPI; import gregtech.api.GTValues; import gregtech.api.items.metaitem.MetaItem; import gregtech.api.metatileentity.MetaTileEntity; -import gregtech.api.recipes.CountableIngredient; -import gregtech.api.recipes.recipeproperties.RecipeProperty; +import gregtech.api.metatileentity.multiblock.CleanroomType; +import gregtech.api.recipes.ingredients.GTRecipeFluidInput; +import gregtech.api.recipes.ingredients.GTRecipeInput; +import gregtech.api.recipes.ingredients.GTRecipeItemInput; +import gregtech.api.recipes.ingredients.GTRecipeOreInput; +import gregtech.api.recipes.ingredients.nbtmatch.NBTCondition; +import gregtech.api.recipes.ingredients.nbtmatch.NBTMatcher; +import gregtech.api.recipes.recipeproperties.*; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.Material; import gregtech.api.unification.ore.OrePrefix; @@ -13,55 +18,50 @@ import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; import gregtech.api.util.ValidationResult; +import gregtech.common.ConfigHolder; import net.minecraft.block.Block; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.Ingredient; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.NonNullList; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import org.apache.commons.lang3.builder.ToStringBuilder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; -/** - * see Recipe - */ - @SuppressWarnings("unchecked") public class ClayRecipeBuilder> { protected ClayRecipeMap recipeMap; - protected final List inputs; - protected final NonNullList outputs; + protected final List inputs; + protected final List outputs; protected final List chancedOutputs; - protected final List fluidInputs; + protected final List fluidInputs; protected final List fluidOutputs; - protected int duration, EUt, tier; + protected int duration, CEt; protected boolean hidden = false; - protected boolean isCTRecipe = false; - protected int parallel = 0; - + protected int tier; protected Consumer> onBuildAction = null; - protected EnumValidationResult recipeStatus = EnumValidationResult.VALID; + protected IRecipePropertyStorage recipePropertyStorage = null; + protected boolean recipePropertyStorageErrored = false; protected ClayRecipeBuilder() { this.inputs = NonNullList.create(); this.outputs = NonNullList.create(); this.chancedOutputs = new ArrayList<>(); - - this.fluidInputs = new ArrayList<>(0); - this.fluidOutputs = new ArrayList<>(0); + this.fluidInputs = new ArrayList<>(); + this.fluidOutputs = new ArrayList<>(); } public ClayRecipeBuilder(ClayRecipe recipe, ClayRecipeMap recipeMap) { @@ -71,14 +71,16 @@ public ClayRecipeBuilder(ClayRecipe recipe, ClayRecipeMap recipeMap) { this.outputs = NonNullList.create(); this.outputs.addAll(GTUtility.copyStackList(recipe.getOutputs())); this.chancedOutputs = new ArrayList<>(recipe.getChancedOutputs()); - - this.fluidInputs = GTUtility.copyFluidList(recipe.getFluidInputs()); + this.fluidInputs = new ArrayList<>(recipe.getFluidInputs()); this.fluidOutputs = GTUtility.copyFluidList(recipe.getFluidOutputs()); - this.duration = recipe.getDuration(); - this.EUt = recipe.getEUt(); - this.tier = recipe.getTier(); + this.CEt = recipe.getEUt(); this.hidden = recipe.isHidden(); + this.tier = recipe.getTier(); + this.recipePropertyStorage = recipe.getRecipePropertyStorage().copy(); + if (this.recipePropertyStorage != null) { + this.recipePropertyStorage.freeze(false); + } } @SuppressWarnings("CopyConstructorMissesField") @@ -89,121 +91,258 @@ protected ClayRecipeBuilder(ClayRecipeBuilder recipeBuilder) { this.outputs = NonNullList.create(); this.outputs.addAll(GTUtility.copyStackList(recipeBuilder.getOutputs())); this.chancedOutputs = new ArrayList<>(recipeBuilder.chancedOutputs); - - this.fluidInputs = GTUtility.copyFluidList(recipeBuilder.getFluidInputs()); + this.fluidInputs = new ArrayList<>(recipeBuilder.getFluidInputs()); this.fluidOutputs = GTUtility.copyFluidList(recipeBuilder.getFluidOutputs()); this.duration = recipeBuilder.duration; - this.EUt = recipeBuilder.EUt; - this.tier = recipeBuilder.EUt; + this.CEt = recipeBuilder.CEt; this.hidden = recipeBuilder.hidden; + this.tier = recipeBuilder.tier; this.onBuildAction = recipeBuilder.onBuildAction; + this.recipePropertyStorage = recipeBuilder.recipePropertyStorage; + if (this.recipePropertyStorage != null) { + this.recipePropertyStorage = this.recipePropertyStorage.copy(); + } } - public boolean applyProperty(String key, Object value) { - return false; + public R cleanroom(@Nullable CleanroomType cleanroom) { + if (!ConfigHolder.machines.enableCleanroom) { + return (R) this; + } + this.applyProperty(CleanroomProperty.getInstance(), cleanroom); + return (R) this; } - public boolean applyProperty(String key, ItemStack item) { + public boolean applyProperty(@Nonnull String key, @Nullable Object value) { + if (key.equals(CleanroomProperty.KEY)) { + if (value instanceof CleanroomType) { + this.cleanroom((CleanroomType) value); + } else if (value instanceof String) { + this.cleanroom(CleanroomType.getByName((String) value)); + } else { + return false; + } + return true; + } return false; } - public R inputs(ItemStack... inputs) { - return inputs(Arrays.asList(inputs)); + public boolean applyProperty(@Nonnull RecipeProperty property, @Nullable Object value) { + if (value == null) { + if (this.recipePropertyStorage != null) { + return this.recipePropertyStorage.remove(property); + } + } else { + if (this.recipePropertyStorage == null) { + this.recipePropertyStorage = new RecipePropertyStorage(); + } + boolean stored = this.recipePropertyStorage.store(property, value); + if (!stored) { + this.recipePropertyStorageErrored = true; + } + return stored; + } + return true; } - public R inputs(Collection inputs) { - if (GTUtility.iterableContains(inputs, stack -> stack == null || stack.isEmpty())) { - GTLog.logger.error("Input cannot contain null or empty ItemStacks. Inputs: {}", inputs); + public R input(GTRecipeInput input) { + if (input.getAmount() < 0) { + GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getAmount()); GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); - recipeStatus = EnumValidationResult.INVALID; + } else { + this.inputs.add(input); } - inputs.forEach(stack -> { - if (!(stack == null || stack.isEmpty())) { - this.inputs.add(CountableIngredient.from(stack)); - } - }); return (R) this; } - public R input(String oredict, int count) { - return inputs(CountableIngredient.from(oredict, count)); + public R input(String oredict) { + return input(GTRecipeOreInput.getOrCreate(oredict, 1)); } - public R input(Enum oredict, int count) { - return inputs(CountableIngredient.from(oredict.name(), count)); + public R input(String oredict, int count) { + return input(GTRecipeOreInput.getOrCreate(oredict, count)); } - public R input(OrePrefix orePrefix, Material material) { - return inputs(CountableIngredient.from(orePrefix, material, 1)); + return input(GTRecipeOreInput.getOrCreate(orePrefix, material, 1)); } public R input(OrePrefix orePrefix, Material material, int count) { - return inputs(CountableIngredient.from(orePrefix, material, count)); + return input(GTRecipeOreInput.getOrCreate(orePrefix, material, count)); } public R input(Item item) { - return input(item, 1); + return input(GTRecipeItemInput.getOrCreate(new ItemStack(item))); } public R input(Item item, int count) { - return inputs(new ItemStack(item, count)); + return input(GTRecipeItemInput.getOrCreate(new ItemStack(item), count)); } public R input(Item item, int count, int meta) { - return inputs(new ItemStack(item, count, meta)); + return input(GTRecipeItemInput.getOrCreate(new ItemStack(item, count, meta))); } - @SuppressWarnings("unused") - public R input(Item item, int count, boolean wild) { - return inputs(new ItemStack(item, count, GTValues.W)); + public R input(Item item, int count, @SuppressWarnings("unused") boolean wild) { + return input(GTRecipeItemInput.getOrCreate(new ItemStack(item, count, GTValues.W))); } - public R input(Block item) { - return input(item, 1); + public R input(Block block) { + return input(block, 1); } - public R input(Block item, int count) { - return inputs(new ItemStack(item, count)); + public R input(Block block, int count) { + return input(GTRecipeItemInput.getOrCreate(new ItemStack(block, count))); } - @SuppressWarnings("unused") - public R input(Block item, int count, boolean wild) { - return inputs(new ItemStack(item, count, GTValues.W)); + public R input(Block block, int count, @SuppressWarnings("unused") boolean wild) { + return input(GTRecipeItemInput.getOrCreate(new ItemStack(block, count, GTValues.W))); } public R input(MetaItem.MetaValueItem item, int count) { - return inputs(item.getStackForm(count)); + return input(GTRecipeItemInput.getOrCreate(item.getStackForm(count))); } public R input(MetaItem.MetaValueItem item) { - return input(item, 1); + return input(GTRecipeItemInput.getOrCreate(item.getStackForm())); } public R input(MetaTileEntity mte) { - return input(mte, 1); + return input(GTRecipeItemInput.getOrCreate(mte.getStackForm())); } public R input(MetaTileEntity mte, int amount) { - return inputs(mte.getStackForm(amount)); + return input(GTRecipeItemInput.getOrCreate(mte.getStackForm(amount))); + } + + public R inputNBT(GTRecipeInput input, NBTMatcher matcher, NBTCondition condition) { + if (input.getAmount() < 0) { + GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getAmount()); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + return (R) this; + } + if (matcher == null) { + GTLog.logger.error("NBTMatcher must not be null"); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + return (R) this; + } + if (condition == null) { + GTLog.logger.error("NBTCondition must not be null"); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + return (R) this; + } + this.inputs.add(input.setNBTMatchingCondition(matcher, condition)); + return (R) this; + } + + public R inputNBT(String oredict, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeOreInput.getOrCreate(oredict, 1), matcher, condition); + } + + public R inputNBT(String oredict, int count, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeOreInput.getOrCreate(oredict, count), matcher, condition); + } + + public R inputNBT(OrePrefix orePrefix, Material material, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeOreInput.getOrCreate(orePrefix, material, 1), matcher, condition); + } + + public R inputNBT(OrePrefix orePrefix, Material material, int count, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeOreInput.getOrCreate(orePrefix, material, count), matcher, condition); + } + + public R inputNBT(Item item, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(item)), matcher, condition); + } + + public R inputNBT(Item item, int count, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(item), count), matcher, condition); + } + + public R inputNBT(Item item, int count, int meta, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(item, count, meta)), matcher, condition); + } + + public R inputNBT(Item item, int count, @SuppressWarnings("unused") boolean wild, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(item, count, GTValues.W)), matcher, condition); + } + + public R inputNBT(Block block, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(block, 1, matcher, condition); + } + + public R inputNBT(Block block, int count, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(block, count)), matcher, condition); } - public R inputs(CountableIngredient... inputs) { - List ingredients = new ArrayList<>(); - for (CountableIngredient input : inputs) { - if (input.getCount() < 0) { - GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getCount()); + public R inputNBT(Block block, int count, @SuppressWarnings("unused") boolean wild, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(new ItemStack(block, count, GTValues.W)), matcher, condition); + } + + public R inputNBT(MetaItem.MetaValueItem item, int count, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(item.getStackForm(count)), matcher, condition); + } + + public R inputNBT(MetaItem.MetaValueItem item, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(item.getStackForm()), matcher, condition); + } + + public R inputNBT(MetaTileEntity mte, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(mte.getStackForm()), matcher, condition); + } + + public R inputNBT(MetaTileEntity mte, int amount, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(GTRecipeItemInput.getOrCreate(mte.getStackForm(amount)), matcher, condition); + } + + public R inputs(ItemStack... inputs) { + for (ItemStack input : inputs) { + if (input == null || input.isEmpty()) { + GTLog.logger.error("Input cannot contain null or empty ItemStacks. Inputs: {}", input); GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); - } else { - ingredients.add(input); + recipeStatus = EnumValidationResult.INVALID; + continue; + } + this.inputs.add(GTRecipeItemInput.getOrCreate(input)); + } + return (R) this; + } + + public R inputStacks(Collection inputs) { + for (ItemStack input : inputs) { + if (input == null || input.isEmpty()) { + GTLog.logger.error("Input cannot contain null or empty ItemStacks. Inputs: {}", input); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + recipeStatus = EnumValidationResult.INVALID; + continue; } + this.inputs.add(GTRecipeItemInput.getOrCreate(input)); } + return (R) this; + } - return inputsIngredients(ingredients); + public R inputs(GTRecipeInput... inputs) { + for (GTRecipeInput input : inputs) { + if (input.getAmount() < 0) { + GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getAmount()); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + recipeStatus = EnumValidationResult.INVALID; + continue; + } + this.inputs.add(input); + } + return (R) this; } - public R inputsIngredients(Collection ingredients) { - this.inputs.addAll(ingredients); + public R inputIngredients(Collection inputs) { + for (GTRecipeInput input : inputs) { + if (input.getAmount() < 0) { + GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getAmount()); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + recipeStatus = EnumValidationResult.INVALID; + continue; + } + this.inputs.add(input); + } return (R) this; } @@ -212,13 +351,18 @@ public R clearInputs() { return (R) this; } + public R notConsumable(GTRecipeInput gtRecipeIngredient) { + return inputs(GTRecipeInput.getOrCreate(gtRecipeIngredient) + .setNonConsumable()); + } + public R notConsumable(ItemStack itemStack) { - return inputs(CountableIngredient.from(itemStack, itemStack.getCount()) + return inputs(GTRecipeItemInput.getOrCreate(itemStack, itemStack.getCount()) .setNonConsumable()); } public R notConsumable(OrePrefix prefix, Material material, int amount) { - return inputs(CountableIngredient.from(prefix, material, amount) + return inputs(GTRecipeOreInput.getOrCreate(prefix, material, amount) .setNonConsumable()); } @@ -226,33 +370,21 @@ public R notConsumable(OrePrefix prefix, Material material) { return notConsumable(prefix, material, 1); } - public R notConsumable(Ingredient ingredient) { - return inputs(new CountableIngredient(ingredient, 1) - .setNonConsumable()); - } - public R notConsumable(MetaItem.MetaValueItem item) { - return inputs(CountableIngredient.from(item.getStackForm(), 1) + return inputs(GTRecipeItemInput.getOrCreate(item.getStackForm(), 1) .setNonConsumable()); } public R notConsumable(Fluid fluid, int amount) { - FluidStack ncf = new FluidStack(fluid, amount, new NBTTagCompound()); - ncf.tag.setBoolean("nonConsumable", true); - return fluidInputs(ncf); + return fluidInputs(GTRecipeFluidInput.getOrCreate(fluid, amount).setNonConsumable()); } public R notConsumable(Fluid fluid) { - return notConsumable(fluid, 1); + return fluidInputs(GTRecipeFluidInput.getOrCreate(fluid, 1).setNonConsumable()); } public R notConsumable(FluidStack fluidStack) { - FluidStack ncf = fluidStack.copy(); - if (ncf.tag == null) { - ncf.tag = new NBTTagCompound(); - } - ncf.tag.setBoolean("nonConsumable", true); - return fluidInputs(ncf); + return fluidInputs(GTRecipeFluidInput.getOrCreate(fluidStack, fluidStack.amount).setNonConsumable()); } public R output(OrePrefix orePrefix, Material material) { @@ -315,18 +447,29 @@ public R clearOutputs() { return (R) this; } - public R fluidInputs(FluidStack... inputs) { - return fluidInputs(Arrays.asList(inputs)); + public R fluidInputs(Collection fluidIngredients) { + this.fluidInputs.addAll(fluidIngredients); + return (R) this; } - public R fluidInputs(Collection inputs) { - if (inputs.contains(null)) { - GTLog.logger.error("Fluid input cannot contain null FluidStacks. Inputs: {}", inputs); - GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); - recipeStatus = EnumValidationResult.INVALID; + public R fluidInputs(GTRecipeInput fluidIngredient) { + this.fluidInputs.add(fluidIngredient); + return (R) this; + } + + public R fluidInputs(FluidStack... fluidStacks) { + ArrayList fluidIngredients = new ArrayList<>(); + for (FluidStack fluidStack : fluidStacks) { + if (fluidStack != null && fluidStack.amount > 0) { + fluidIngredients.add(GTRecipeFluidInput.getOrCreate(fluidStack, fluidStack.amount)); + } else if (fluidStack != null) { + GTLog.logger.error("Count cannot be less than 0. Actual: {}.", fluidStack.amount); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + } else { + GTLog.logger.error("FluidStack cannot be null."); + } } - this.fluidInputs.addAll(inputs); - this.fluidInputs.removeIf(Objects::isNull); + this.fluidInputs.addAll(fluidIngredients); return (R) this; } @@ -413,12 +556,12 @@ public void chancedOutputsMultiply(ClayRecipe chancedOutputsFrom, int numberOfOp } /** - * Appends the passed {link Recipe} onto the inputs and outputs, multiplied by the amount specified by multiplier - * The duration of the multiplied {link Recipe} is also added to the current duration + * Appends the passed {@link ClayRecipe} onto the inputs and outputs, multiplied by the amount specified by multiplier + * The duration of the multiplied {@link ClayRecipe} is also added to the current duration * * @param recipe The Recipe to be multiplied * @param multiplier Amount to multiply the recipe by - * @param multiplyDuration Whether duration should be multiplied instead of CEt + * @param multiplyDuration Whether duration should be multiplied instead of EUt * @return the builder holding the multiplied recipe */ @@ -428,8 +571,8 @@ public R append(ClayRecipe recipe, int multiplier, boolean multiplyDuration) { } // Create holders for the various parts of the new multiplied Recipe - List newRecipeInputs = new ArrayList<>(); - List newFluidInputs = new ArrayList<>(); + List newRecipeInputs = new ArrayList<>(); + List newFluidInputs = new ArrayList<>(); List outputItems = new ArrayList<>(); List outputFluids = new ArrayList<>(); @@ -437,7 +580,7 @@ public R append(ClayRecipe recipe, int multiplier, boolean multiplyDuration) { multiplyInputsAndOutputs(newRecipeInputs, newFluidInputs, outputItems, outputFluids, recipe, multiplier); // Build the new Recipe with multiplied components - this.inputsIngredients(newRecipeInputs); + this.inputIngredients(newRecipeInputs); this.fluidInputs(newFluidInputs); this.outputs(outputItems); @@ -445,37 +588,32 @@ public R append(ClayRecipe recipe, int multiplier, boolean multiplyDuration) { this.fluidOutputs(outputFluids); - this.CEt(multiplyDuration ? recipe.getEUt() : this.EUt + recipe.getEUt() * multiplier); + this.CEt(multiplyDuration ? recipe.getEUt() : this.CEt + recipe.getEUt() * multiplier); this.duration(multiplyDuration ? this.duration + recipe.getDuration() * multiplier : recipe.getDuration()); this.parallel += multiplier; return (R) this; } - protected static void multiplyInputsAndOutputs(List newRecipeInputs, - List newFluidInputs, + protected static void multiplyInputsAndOutputs(List newRecipeInputs, + List newFluidInputs, List outputItems, List outputFluids, ClayRecipe recipe, int numberOfOperations) { - recipe.getInputs().forEach(ci -> { - if (ci.isNonConsumable()) { - newRecipeInputs.add(new CountableIngredient(ci.getIngredient(), - ci.getCount()).setNonConsumable()); + recipe.getInputs().forEach(ri -> { + if (ri.isNonConsumable()) { + newRecipeInputs.add(ri); } else { - newRecipeInputs.add(new CountableIngredient(ci.getIngredient(), - ci.getCount() * numberOfOperations)); + newRecipeInputs.add(ri.copyWithAmount(ri.getAmount() * numberOfOperations)); } }); - recipe.getFluidInputs().forEach(fluidStack -> { - if (fluidStack.tag != null && fluidStack.tag.hasKey("nonConsumable")) { - FluidStack fs = new FluidStack(fluidStack.getFluid(), fluidStack.amount, new NBTTagCompound()); - fs.tag.setBoolean("nonConsumable", true); - newFluidInputs.add(fs); + recipe.getFluidInputs().forEach(fi -> { + if (fi.isNonConsumable()) { + newFluidInputs.add(fi); } else { - newFluidInputs.add(new FluidStack(fluidStack.getFluid(), - fluidStack.amount * (fluidStack.tag != null && fluidStack.tag.hasKey("nonConsumable") ? 1 : numberOfOperations))); + newFluidInputs.add(fi.copyWithAmount(fi.getAmount() * numberOfOperations)); } }); @@ -510,12 +648,7 @@ public R duration(int duration) { } public R CEt(int CEt) { - this.EUt = CEt; - return (R) this; - } - - public R tier(int tier){ - this.tier = tier; + this.CEt = CEt; return (R) this; } @@ -529,6 +662,11 @@ public R isCTRecipe() { return (R) this; } + public R tier(int tier){ + this.tier = tier; + return (R) this; + } + public R setRecipeMap(ClayRecipeMap recipeMap) { this.recipeMap = recipeMap; return (R) this; @@ -539,39 +677,36 @@ public R copy() { } protected EnumValidationResult finalizeAndValidate() { - return validate(); + return recipePropertyStorageErrored ? EnumValidationResult.INVALID : validate(); } public ValidationResult build() { - return ValidationResult.newResult(finalizeAndValidate(), - new ClayRecipe(inputs, outputs, chancedOutputs, fluidInputs, fluidOutputs, duration, EUt, hidden, isCTRecipe, tier)); + return ValidationResult.newResult(finalizeAndValidate(), new ClayRecipe(inputs, outputs, chancedOutputs, + fluidInputs, fluidOutputs, duration, CEt, hidden, isCTRecipe, + recipePropertyStorage == null ? EmptyRecipePropertyStorage.INSTANCE : recipePropertyStorage, tier)); } protected EnumValidationResult validate() { - if (EUt == 0) { + if (CEt == 0) { GTLog.logger.error("EU/t cannot be equal to 0", new IllegalArgumentException()); - if(isCTRecipe) { + if (isCTRecipe) { CraftTweakerAPI.logError("EU/t cannot be equal to 0", new IllegalArgumentException()); } recipeStatus = EnumValidationResult.INVALID; } if (duration <= 0) { GTLog.logger.error("Duration cannot be less or equal to 0", new IllegalArgumentException()); - if(isCTRecipe) { + if (isCTRecipe) { CraftTweakerAPI.logError("Duration cannot be less or equal to 0", new IllegalArgumentException()); } recipeStatus = EnumValidationResult.INVALID; } - if (tier < 0) { - GTLog.logger.error("Tier cannot be less than 0", new IllegalArgumentException()); - if(isCTRecipe) { - CraftTweakerAPI.logError("DTier cannot be less than 0", new IllegalArgumentException()); - } - recipeStatus = EnumValidationResult.INVALID; - } if (recipeStatus == EnumValidationResult.INVALID) { GTLog.logger.error("Invalid recipe, read the errors above: {}", this); } + if (recipePropertyStorage != null) { + recipePropertyStorage.freeze(true); + } return recipeStatus; } @@ -586,8 +721,9 @@ protected R invalidateOnBuildAction() { } public void buildAndRegister() { - if (onBuildAction != null) + if (onBuildAction != null) { onBuildAction.accept(this); + } ValidationResult validationResult = build(); recipeMap.addRecipe(validationResult); } @@ -596,7 +732,7 @@ public void buildAndRegister() { // Getters // /////////////////// - public List getInputs() { + public List getInputs() { return inputs; } @@ -609,7 +745,7 @@ public List getChancedOutputs() { } /** - * Similar to {link Recipe#getAllItemOutputs()}, returns the recipe outputs and all chanced outputs + * Similar to {@link ClayRecipe#getAllItemOutputs()}, returns the recipe outputs and all chanced outputs * * @return A List of ItemStacks composed of the recipe outputs and chanced outputs */ @@ -621,7 +757,7 @@ public List getAllItemOutputs() { return stacks; } - public List getFluidInputs() { + public List getFluidInputs() { return fluidInputs; } @@ -630,15 +766,21 @@ public List getFluidOutputs() { } public int getEUt() { - return EUt; + return CEt; + } + + public int getDuration() { + return duration; } - public int getTier() { + public int getTier(){ return tier; } - public int getDuration() { - return duration; + @Nullable + public CleanroomType getCleanroom() { + return this.recipePropertyStorage == null ? null : + this.recipePropertyStorage.getRecipePropertyValue(CleanroomProperty.getInstance(), null); } @Override @@ -651,9 +793,9 @@ public String toString() { .append("fluidInputs", fluidInputs) .append("fluidOutputs", fluidOutputs) .append("duration", duration) - .append("CEt", EUt) - .append("Tier", tier) + .append("CEt", CEt) .append("hidden", hidden) + .append("cleanroom", getCleanroom()) .append("recipeStatus", recipeStatus) .toString(); } diff --git a/src/main/java/clayium/api/recipes/ClayRecipeMap.java b/src/main/java/clayium/api/recipes/ClayRecipeMap.java index 722a9e5..328f1a6 100644 --- a/src/main/java/clayium/api/recipes/ClayRecipeMap.java +++ b/src/main/java/clayium/api/recipes/ClayRecipeMap.java @@ -3,6 +3,7 @@ import clayium.api.gui.widgets.ClayRecipeProgressWidget; import clayium.api.recipes.crafttweaker.ClayCTRecipe; import clayium.api.recipes.crafttweaker.ClayCTRecipeBuilder; +import clayium.api.recipes.map.ClayBranch; import com.google.common.collect.ImmutableList; import crafttweaker.CraftTweakerAPI; import crafttweaker.annotations.ZenRegister; @@ -21,34 +22,45 @@ import gregtech.api.gui.widgets.SlotWidget; import gregtech.api.gui.widgets.TankWidget; import gregtech.api.recipes.*; +import gregtech.api.recipes.ingredients.GTRecipeInput; +import gregtech.api.recipes.map.*; import gregtech.api.unification.material.Material; import gregtech.api.unification.ore.OrePrefix; import gregtech.api.util.*; import gregtech.common.ConfigHolder; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.SoundEvent; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fml.common.Optional.Method; import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.oredict.OreDictionary; import stanhebben.zenscript.annotations.Optional; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenGetter; import stanhebben.zenscript.annotations.ZenMethod; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.util.*; import java.util.function.Consumer; import java.util.function.DoubleSupplier; +import java.util.function.Predicate; import java.util.stream.Collectors; @ZenClass("mods.clayium.recipe.ClayRecipeMap") @ZenRegister public class ClayRecipeMap> { - private static final Map> RECIPE_MAP_REGISTRY = new HashMap<>(); - public IChanceFunction chanceFunction = (chance, boostPerTier, tier) -> chance + (boostPerTier * tier); + private static final Map> RECIPE_MAP_REGISTRY = new Object2ReferenceOpenHashMap<>(); + private static final Comparator RECIPE_DURATION_THEN_EU = Comparator.comparingInt(ClayRecipe::getDuration).thenComparingInt(ClayRecipe::getEUt).thenComparing(ClayRecipe::hashCode); + private static final IChanceFunction DEFAULT_CHANCE_FUNCTION = (chance, boostPerTier, tier) -> chance + (boostPerTier * tier); + + public IChanceFunction chanceFunction = DEFAULT_CHANCE_FUNCTION; public final String unlocalizedName; @@ -65,26 +77,18 @@ public class ClayRecipeMap> { public final boolean isHidden; public final boolean exactTier; - private final Object2ObjectOpenHashMap> recipeFluidMap = new Object2ObjectOpenHashMap<>(); - private final Object2ObjectOpenHashMap> recipeItemMap = new Object2ObjectOpenHashMap<>(); - - private static final Comparator RECIPE_DURATION_THEN_EU = - Comparator.comparingInt(ClayRecipe::getDuration) - .thenComparingInt(ClayRecipe::getEUt) - .thenComparing(ClayRecipe::hashCode); + private final ClayBranch lookup = new ClayBranch(); + private boolean hasOreDictedInputs = false; + private boolean hasNBTMatcherInputs = false; + private static final WeakHashMap> ingredientRoot = new WeakHashMap<>(); + private final WeakHashMap> fluidIngredientRoot = new WeakHashMap<>(); - private final Set recipeSet = new TreeSet<>(RECIPE_DURATION_THEN_EU); private Consumer> onRecipeBuildAction; - protected SoundEvent sound; - private ClayRecipeMap smallRecipeMap; - public ClayRecipeMap(String unlocalizedName, - int minInputs, int maxInputs, int minOutputs, int maxOutputs, - int minFluidInputs, int maxFluidInputs, int minFluidOutputs, int maxFluidOutputs, - R defaultRecipe, boolean isHidden, boolean exactTier) { + public ClayRecipeMap(String unlocalizedName, int minInputs, int maxInputs, int minOutputs, int maxOutputs, int minFluidInputs, int maxFluidInputs, int minFluidOutputs, int maxFluidOutputs, R defaultRecipe, boolean isHidden, boolean isExactTier) { this.unlocalizedName = unlocalizedName; this.slotOverlays = new TByteObjectHashMap<>(); this.progressBarTexture = GuiTextures.PROGRESS_BAR_ARROW; @@ -101,7 +105,7 @@ public ClayRecipeMap(String unlocalizedName, this.maxFluidOutputs = maxFluidOutputs; this.isHidden = isHidden; - this.exactTier = exactTier; + this.exactTier = isExactTier; defaultRecipe.setRecipeMap(this); this.recipeBuilderSample = defaultRecipe; RECIPE_MAP_REGISTRY.put(unlocalizedName, this); @@ -131,9 +135,7 @@ public static void setFoundInvalidRecipe(boolean foundInvalidRecipe) { OrePrefix currentOrePrefix = OrePrefix.getCurrentProcessingPrefix(); if (currentOrePrefix != null) { Material currentMaterial = OrePrefix.getCurrentMaterial(); - GTLog.logger.error("Error happened during processing ore registration of prefix {} and material {}. " + - "Seems like cross-mod compatibility issue. Report to GTCEu github.", - currentOrePrefix, currentMaterial); + GTLog.logger.error("Error happened during processing ore registration of prefix {} and material {}. " + "Seems like cross-mod compatibility issue. Report to GTCEu github.", currentOrePrefix, currentMaterial); } } @@ -144,9 +146,7 @@ public ClayRecipeMap setProgressBar(TextureArea progressBar, MoveType moveTyp } public ClayRecipeMap setSlotOverlay(boolean isOutput, boolean isFluid, TextureArea slotOverlay) { - return this - .setSlotOverlay(isOutput, isFluid, false, slotOverlay) - .setSlotOverlay(isOutput, isFluid, true, slotOverlay); + return this.setSlotOverlay(isOutput, isFluid, false, slotOverlay).setSlotOverlay(isOutput, isFluid, true, slotOverlay); } public ClayRecipeMap setSlotOverlay(boolean isOutput, boolean isFluid, boolean isLast, TextureArea slotOverlay) { @@ -189,11 +189,7 @@ public boolean canInputFluidForce(Fluid fluid) { } public Collection getRecipesForFluid(FluidStack fluid) { - return recipeFluidMap.getOrDefault(new FluidKey(fluid), Collections.emptySet()); - } - - public Collection getRecipesForFluid(FluidKey fluidKey) { - return recipeFluidMap.getOrDefault(fluidKey, Collections.emptySet()); + return lookup.getRecipes(false).filter(r -> r.hasInputFluid(fluid)).collect(Collectors.toSet()); } private static boolean foundInvalidRecipe = false; @@ -209,53 +205,22 @@ public void addRecipe(ValidationResult validationResult) { return; } ClayRecipe recipe = validationResult.getResult(); - if (recipeSet.add(recipe)) { - for (CountableIngredient countableIngredient : recipe.getInputs()) { - ItemStack[] stacks = countableIngredient.getIngredient().getMatchingStacks(); - for (ItemStack itemStack : stacks) { - ItemStackKey stackKey = KeySharedStack.getRegisteredStack(itemStack); - recipeItemMap.computeIfPresent(stackKey, (k, v) -> { - v.add(recipe); - return v; - }); - recipeItemMap.computeIfAbsent(stackKey, k -> new HashSet<>()).add(recipe); - } - } - for (FluidStack fluid : recipe.getFluidInputs()) { - if (fluid.tag != null && fluid.tag.hasKey("nonConsumable")) { - fluid = fluid.copy(); - fluid.tag.removeTag("nonConsumable"); - if (fluid.tag.isEmpty()) { - fluid.tag = null; - } - } - FluidKey fluidKey = new FluidKey(fluid); - recipeFluidMap.computeIfPresent(fluidKey, (k, v) -> { - v.add(recipe); - return v; - }); - recipeFluidMap.computeIfAbsent(fluidKey, k -> new HashSet<>()).add(recipe); - } - } else if (ConfigHolder.misc.debug) { - GTLog.logger.warn("Recipe: {} for Recipe Map {} is a duplicate and was not added", recipe.toString(), this.unlocalizedName); - if(recipe.getIsCTRecipe()) { - CraftTweakerAPI.logError(String.format("Recipe: %s for Recipe Map %s is a duplicate and was not added", recipe.toString(), this.unlocalizedName)); - } + + compileRecipe(recipe); + + } + + public void compileRecipe(ClayRecipe recipe) { + if (recipe == null) { + return; } + List> items = fromRecipe(recipe); + recurseIngredientTreeAdd(recipe, items, lookup, 0, 0); } public boolean removeRecipe(ClayRecipe recipe) { - //if we actually removed this recipe - if (recipeSet.remove(recipe)) { - //also iterate trough fluid mappings and remove recipe from them - recipeFluidMap.values().forEach(fluidMap -> - fluidMap.removeIf(fluidRecipe -> fluidRecipe == recipe)); - recipeItemMap.values().forEach(itemMap -> - itemMap.removeIf(itemRecipe -> itemRecipe == recipe)); - - return true; - } - return false; + List> items = fromRecipe(recipe); + return recurseIngredientTreeRemove(recipe, items, lookup, 0) != null; } protected ValidationResult postValidateRecipe(ValidationResult validationResult) { @@ -264,7 +229,7 @@ protected ValidationResult postValidateRecipe(ValidationResult postValidateRecipe(ValidationResult postValidateRecipe(ValidationResult postValidateRecipe(ValidationResult postValidateRecipe(ValidationResult inputs, List fluidInputs, int outputFluidTankCapacity) { - return findRecipe(tier, inputs, fluidInputs, outputFluidTankCapacity, exactTier); + public ClayRecipe findRecipe(long voltage, List inputs, List fluidInputs, int outputFluidTankCapacity) { + return findRecipe(voltage, inputs, fluidInputs, outputFluidTankCapacity, false); } /** * Finds a Recipe matching the Fluid and/or ItemStack Inputs. * - * @param tier Tier of the Machine or Long.MAX_VALUE if it has no Tier + * @param voltage Voltage of the Machine or Long.MAX_VALUE if it has no Voltage * @param inputs the Item Inputs * @param fluidInputs the Fluid Inputs * @param outputFluidTankCapacity minimal capacity of output fluid tank, used for fluid canner recipes for example - * @param exactTier should require exact voltage matching on recipe. + * @param exactVoltage should require exact voltage matching on recipe. used by craftweaker * @return the Recipe it has found or null for no matching Recipe */ @Nullable - public ClayRecipe findRecipe(long tier, List inputs, List fluidInputs, int outputFluidTankCapacity, boolean exactTier) { - if (recipeSet.isEmpty()) + public ClayRecipe findRecipe(long voltage, List inputs, List fluidInputs, int outputFluidTankCapacity, boolean exactVoltage) { + return find(inputs.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList()), fluidInputs.stream().filter(Objects::nonNull).collect(Collectors.toList()), recipe -> { + if (exactVoltage && recipe.getTier() != voltage) { + return false; + } + return recipe.getTier() <= voltage && recipe.matches(false, inputs, fluidInputs); + }); + } + + public boolean acceptsFluid(List fluidInputs, FluidStack fluid) { + if (canInputFluidForce(fluid.getFluid())) { + return true; + } + if (fluidInputs.isEmpty()) { + return fluidIngredientRoot.get(new MapFluidIngredient(fluid)) != null; + } + if (fluidInputs.contains(fluid)) { + return true; + } + fluidInputs.add(fluid); + List> list = new ObjectArrayList<>(); + buildFromFluidStacks(list, fluidInputs); + return canInsertFluid(list, lookup); + } + + @Nullable + public ClayRecipe find(@Nonnull List items, @Nonnull List fluids, @Nonnull Predicate canHandle) { + // First, check if items and fluids are valid. + if (items.size() == Integer.MAX_VALUE || fluids.size() == Integer.MAX_VALUE) { return null; - if (minFluidInputs > 0 && GTUtility.amountOfNonNullElements(fluidInputs) < minFluidInputs) { + } + if (items.size() == 0 && fluids.size() == 0) { return null; } - if (minInputs > 0 && GTUtility.amountOfNonEmptyStacks(inputs) < minInputs) { + // Filter out empty fluids. + + // Build input. + List> list = new ObjectArrayList<>(items.size() + fluids.size()); + if (items.size() > 0) { + buildFromItemStacks(list, uniqueItems(items)); + } + if (fluids.size() > 0) { + List stack = new ObjectArrayList<>(fluids.size()); + for (FluidStack f : fluids) { + if (f == null || f.amount == 0) { + continue; + } + stack.add(f); + } + if (stack.size() > 0) { + buildFromFluidStacks(list, stack); + } + } + if (list.size() == 0) { return null; } - return findByInputsAndFluids(tier, inputs, fluidInputs, exactTier); + return recurseIngredientTreeFindRecipe(list, lookup, canHandle); } - @Nullable - private ClayRecipe findByInputsAndFluids(long tier, List inputs, List fluidInputs, boolean exactTier) { - HashSet iteratedRecipes = new HashSet<>(); - HashSet searchedItems = new HashSet<>(); - HashSet searchedFluids = new HashSet<>(); - HashMap> priorityRecipeMap = new HashMap<>(); - HashMap promotedTimes = new HashMap<>(); - - for (ItemStack stack : inputs) { - if (!stack.isEmpty()) { - ItemStackKey itemStackKey = KeySharedStack.getRegisteredStack(stack); - if (!searchedItems.contains(itemStackKey) && recipeItemMap.containsKey(itemStackKey)) { - searchedItems.add(itemStackKey); - for (ClayRecipe tmpRecipe : recipeItemMap.get(itemStackKey)) { - if (!exactTier && tier < tmpRecipe.getTier()) { - continue; - } else if (exactTier && tier != tmpRecipe.getTier()) { - continue; - } - calculateRecipePriority(tmpRecipe, promotedTimes, priorityRecipeMap); - } + /** + * Builds a list of unique ItemStacks from the given Collection of ItemStacks. + * Used to reduce the number inputs, if for example there is more than one of the same input, + * pack them into one. + * This uses a strict comparison, so it will not pack the same item with different NBT tags, + * to allow the presence of, for example, more than one configured circuit in the input. + * @param input The Collection of GTRecipeInputs. + * @return an array of unique itemstacks. + */ + + public static ItemStack[] uniqueItems(Collection input) { + List list = new ObjectArrayList<>(input.size()); + for (ItemStack item : input) { + if (item.isEmpty()) { + continue; + } + boolean isEqual = false; + for (ItemStack obj: list) { + if (item.isItemEqual(obj) && ItemStack.areItemStackTagsEqual(item, obj)) { + isEqual = true; + break; } } + if (isEqual) continue; + list.add(item); } + return list.toArray(new ItemStack[0]); + } - for (FluidStack fluidStack : fluidInputs) { - if (fluidStack != null) { - FluidKey fluidKey = new FluidKey(fluidStack); - if (!searchedFluids.contains(fluidKey) && recipeFluidMap.containsKey(fluidKey)) { - searchedFluids.add(fluidKey); - for (ClayRecipe tmpRecipe : recipeFluidMap.get(fluidKey)) { - if (!exactTier && tier < tmpRecipe.getTier()) { - continue; - } else if (exactTier && tier != tmpRecipe.getTier()) { - continue; - } - calculateRecipePriority(tmpRecipe, promotedTimes, priorityRecipeMap); - } + /** + * Builds a list of unique inputs from the given list GTRecipeInputs. + * Used to reduce the number inputs, if for example there is more than one of the same input, + * pack them into one. + * @param input The list of GTRecipeInputs. + * @return The list of unique inputs. + */ + + public static List uniqueIngredientsList(List input) { + List list = new ObjectArrayList<>(input.size()); + for (GTRecipeInput item : input) { + boolean isEqual = false; + for (GTRecipeInput obj : list) { + if (item.equalIgnoreAmount(obj)) { + isEqual = true; + break; } } + if (isEqual) continue; + list.add(item); } + return list; + } - return prioritizedRecipe(priorityRecipeMap, iteratedRecipes, inputs, fluidInputs); + /** + * Returns a boolean indicating whether the given group of fluids resolves to a valid branch or recipe. + * + * @param fluidIngredients the ingredients part + * @param map the root branch to search from. + * @return a recipe + */ + private boolean canInsertFluid(@Nonnull List> fluidIngredients, @Nonnull ClayBranch map) { + // Try each ingredient as a starting point, adding it to the skiplist. + boolean canInsert; + for (int i = 0; i < fluidIngredients.size(); i++) { + canInsert = recurseFluidTreeFindBranchOrRecipe(fluidIngredients, map, i, 0, (1L << i)); + if (canInsert) { + return true; + } + } + return false; } - private ClayRecipe prioritizedRecipe(Map> priorityRecipeMap, HashSet iteratedRecipes, List inputs, List fluidInputs) { - for (int i = priorityRecipeMap.size() - 1; i >= 0; i--) { - if (priorityRecipeMap.containsKey(i)) { - for (ClayRecipe tmpRecipe : priorityRecipeMap.get(i)) { - if (iteratedRecipes.add(tmpRecipe)) { - if (tmpRecipe.matches(false, inputs, fluidInputs)) { - return tmpRecipe; - } + /** + * Recursively finds either a recipe or a branch, and return it upon evaluating all the ingredients + * + * @param fluidIngredients the ingredients part + * @param branchMap the current branch of the tree + * @param index the index of the wrapper to get + * @param count how deep we are in recursion, < ingredients.length + * @param skip bitmap of ingredients to skip, i.e. which ingredients are used in the + * recursion. + * @return True if the current fluid ingredients resolve to a valid branch or recipe. False otherwise. + */ + private boolean recurseFluidTreeFindBranchOrRecipe(@Nonnull List> fluidIngredients, @Nonnull ClayBranch branchMap, int index, int count, long skip) { + List wr = fluidIngredients.get(index); + // Iterate over current level of nodes. + for (AbstractMapIngredient t : wr) { + Either result = branchMap.getNodes().get(t); + if (result != null) { + if (result.left().isPresent() && count == fluidIngredients.size() - 1) { + return true; + } else if (result.right().isPresent()) { + if (count == fluidIngredients.size()) { + return true; } + return diveFluidTreeFindBranchOrRecipe(fluidIngredients, result.right().get(), index, count, skip); + } + } + } + return false; + } + + private boolean diveFluidTreeFindBranchOrRecipe(@Nonnull List> fluidIngredients, @Nonnull ClayBranch branchMap, int index, int count, long skip) { + // We loop around fluidIngredients.size() if we reach the end. + int counter = (index + 1) % fluidIngredients.size(); + while (counter != index) { + // Have we already used this ingredient? If so, skip this one. + if (((skip & (1L << counter)) == 0)) { + // Recursive call. + boolean found = recurseFluidTreeFindBranchOrRecipe(fluidIngredients, branchMap, counter, count + 1, skip | (1L << counter)); + if (found) { + return true; + } + } + counter = (counter + 1) % fluidIngredients.size(); + } + return false; + } + + /** + * Recursively finds a recipe, top level. call this to find a recipe + * + * @param ingredients the ingredients part + * @param branchRoot the root branch to search from. + * @return a recipe + */ + private ClayRecipe recurseIngredientTreeFindRecipe(@Nonnull List> ingredients, @Nonnull ClayBranch branchRoot, @Nonnull Predicate canHandle) { + // Try each ingredient as a starting point, adding it to the skiplist. + for (int i = 0; i < ingredients.size(); i++) { + ClayRecipe r = recurseIngredientTreeFindRecipe(ingredients, branchRoot, canHandle, i, 0, (1L << i)); + if (r != null) { + return r; + } + } + return null; + } + + /** + * Recursively finds a recipe + * + * @param ingredients the ingredients part + * @param branchMap the current branch of the tree + * @param canHandle predicate to test found recipe. + * @param index the index of the wrapper to get + * @param count how deep we are in recursion, < ingredients.length + * @param skip bitmap of ingredients to skip, i.e. which ingredients are used in the + * recursion. + * @return a recipe + */ + private ClayRecipe recurseIngredientTreeFindRecipe(@Nonnull List> ingredients, @Nonnull ClayBranch branchMap, @Nonnull Predicate canHandle, int index, int count, long skip) { + if (count == ingredients.size()) { + return null; + } + List wr = ingredients.get(index); + // Iterate over current level of nodes. + for (AbstractMapIngredient t : wr) { + Map> targetMap; + if (t.isSpecialIngredient()) { + targetMap = branchMap.getSpecialNodes(); + } else { + targetMap = branchMap.getNodes(); + } + + Either result = targetMap.get(t); + if (result != null) { + // Either return recipe or continue branch. + ClayRecipe r = result.map(recipe -> canHandle.test(recipe) ? recipe : null, right -> diveIngredientTreeFindRecipe(ingredients, right, canHandle, index, count, skip)); + if (r != null) { + return r; + } + } + } + return null; + } + + private ClayRecipe diveIngredientTreeFindRecipe(@Nonnull List> ingredients, @Nonnull ClayBranch map, Predicate canHandle, int index, int count, long skip) { + // We loop around ingredients.size() if we reach the end. + int counter = (index + 1) % ingredients.size(); + while (counter != index) { + // Have we already used this ingredient? If so, skip this one. + if (((skip & (1L << counter)) == 0)) { + // Recursive call. + ClayRecipe found = recurseIngredientTreeFindRecipe(ingredients, map, canHandle, counter, count + 1, skip | (1L << counter)); + if (found != null) { + return found; + } + } + counter = (counter + 1) % ingredients.size(); + } + return null; + } + + /** + * Exhaustively gathers all recipes that can be crafted with the given ingredients, into a Set. + * @param items the ingredients, in the form of a List of ItemStack. Usually the inputs of a Recipe + * @param fluids the ingredients, in the form of a List of FluidStack. Usually the inputs of a Recipe + * @return a Set of recipes that can be crafted with the given ingredients + */ + + @Nullable + public Set findRecipeCollisions(List items, List fluids) { + // First, check if items and fluids are valid. + if (items.size() == Integer.MAX_VALUE || fluids.size() == Integer.MAX_VALUE) { + return null; + } + if (items.size() == 0 && fluids.size() == 0) { + return null; + } + // Filter out empty fluids. + + // Build input. + List> list = new ObjectArrayList<>(items.size() + fluids.size()); + if (items.size() > 0) { + buildFromItemStacks(list, uniqueItems(items)); + } + if (fluids.size() > 0) { + List stack = new ObjectArrayList<>(fluids.size()); + for (FluidStack f : fluids) { + if (f == null || f.amount == 0) { + continue; } + stack.add(f); + } + if (stack.size() > 0) { + buildFromFluidStacks(list, stack); } } + if (list.size() == 0) { + return null; + } + Set collidingRecipes = new HashSet<>(); + return recurseIngredientTreeFindRecipeCollisions(list, lookup, collidingRecipes); + } + + private Set recurseIngredientTreeFindRecipeCollisions(@Nonnull List> ingredients, @Nonnull ClayBranch branchRoot, Set collidingRecipes) { + // Try each ingredient as a starting point, adding it to the skiplist. + for (int i = 0; i < ingredients.size(); i++) { + recurseIngredientTreeFindRecipeCollisions(ingredients, branchRoot, i, 0, (1L << i), collidingRecipes); + } + return collidingRecipes; + } + + private ClayRecipe recurseIngredientTreeFindRecipeCollisions(@Nonnull List> ingredients, @Nonnull ClayBranch branchMap, int index, int count, long skip, Set collidingRecipes) { + if (count == ingredients.size()) { + return null; + } + List wr = ingredients.get(index); + // Iterate over current level of nodes. + for (AbstractMapIngredient t : wr) { + Map> targetMap; + if (t.isSpecialIngredient()) { + targetMap = branchMap.getSpecialNodes(); + } else { + targetMap = branchMap.getNodes(); + } + Either result = targetMap.get(t); + if (result != null) { + // Either return recipe or continue branch. + ClayRecipe r = result.map(recipe -> recipe, right -> diveIngredientTreeFindRecipeCollisions(ingredients, right, index, count, skip, collidingRecipes)); + if (r != null) { + collidingRecipes.add(r); + } + } + } return null; } - private void calculateRecipePriority(ClayRecipe recipe, HashMap promotedTimes, Map> priorityRecipeMap) { - Integer p = promotedTimes.get(recipe); - if (p == null) { - p = 0; + private ClayRecipe diveIngredientTreeFindRecipeCollisions(@Nonnull List> ingredients, @Nonnull ClayBranch map, int index, int count, long skip, Set collidingRecipes) { + // We loop around ingredients.size() if we reach the end. + int counter = (index + 1) % ingredients.size(); + while (counter != index) { + // Have we already used this ingredient? If so, skip this one. + if (((skip & (1L << counter)) == 0)) { + // Recursive call. + ClayRecipe r = recurseIngredientTreeFindRecipeCollisions(ingredients, map, counter, count + 1, skip | (1L << counter), collidingRecipes); + if (r != null) { + return r; + } + } + counter = (counter + 1) % ingredients.size(); } - promotedTimes.put(recipe, p + 1); - priorityRecipeMap.computeIfAbsent(p, k -> new LinkedList<>()); - priorityRecipeMap.get(p).add(recipe); + return null; } public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { @@ -419,8 +645,7 @@ public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, builder.widget(new ClayRecipeProgressWidget(200, 78, 23 + yOffset, 20, 20, progressBarTexture, moveType, this)); addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); - if (this.specialTexture != null && this.specialTexturePosition != null) - addSpecialTexture(builder); + if (this.specialTexture != null && this.specialTexturePosition != null) addSpecialTexture(builder); return builder; } @@ -430,8 +655,7 @@ public ModularUI.Builder createUITemplate(DoubleSupplier progressSupplier, IItem builder.widget(new ClayRecipeProgressWidget(progressSupplier, 78, 23 + yOffset, 20, 20, progressBarTexture, moveType, this)); addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); - if (this.specialTexture != null && this.specialTexturePosition != null) - addSpecialTexture(builder); + if (this.specialTexture != null && this.specialTexturePosition != null) addSpecialTexture(builder); return builder; } @@ -440,8 +664,7 @@ public ModularUI.Builder createUITemplateNoOutputs(DoubleSupplier progressSuppli ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); builder.widget(new ClayRecipeProgressWidget(progressSupplier, 78, 23 + yOffset, 20, 20, progressBarTexture, moveType, this)); addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); - if (this.specialTexture != null && this.specialTexturePosition != null) - addSpecialTexture(builder); + if (this.specialTexture != null && this.specialTexturePosition != null) addSpecialTexture(builder); return builder; } @@ -493,13 +716,9 @@ protected void addInventorySlotGroup(ModularUI.Builder builder, IItemHandlerModi protected void addSlot(ModularUI.Builder builder, int x, int y, int slotIndex, IItemHandlerModifiable itemHandler, FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) { if (!isFluid) { - builder.widget(new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs) - .setBackgroundTexture(getOverlaysForSlot(isOutputs, false, slotIndex == itemHandler.getSlots() - 1))); + builder.widget(new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs).setBackgroundTexture(getOverlaysForSlot(isOutputs, false, slotIndex == itemHandler.getSlots() - 1))); } else { - builder.widget(new TankWidget(fluidHandler.getTankAt(slotIndex), x, y, 18, 18) - .setAlwaysShowFull(true) - .setBackgroundTexture(getOverlaysForSlot(isOutputs, true, slotIndex == fluidHandler.getTanks() - 1)) - .setContainerClicking(true, !isOutputs)); + builder.widget(new TankWidget(fluidHandler.getTankAt(slotIndex), x, y, 18, 18).setAlwaysShowFull(true).setBackgroundTexture(getOverlaysForSlot(isOutputs, true, slotIndex == fluidHandler.getTanks() - 1)).setContainerClicking(true, !isOutputs)); } } @@ -537,6 +756,174 @@ protected static int[] determineSlotsGrid(int itemInputsCount) { return new int[]{itemSlotsToLeft, itemSlotsToDown}; } + /** + * Adds a recipe to the map. (recursive part) + * + * @param recipe the recipe to add. + * @param ingredients list of input ingredients. + * @param branchMap the current branch in the recursion. + * @param index where in the ingredients list we are. + * @param count how many added already. + */ + boolean recurseIngredientTreeAdd(@Nonnull ClayRecipe recipe, @Nonnull List> ingredients, @Nonnull ClayBranch branchMap, int index, int count) { + if (count >= ingredients.size()) return true; + if (index >= ingredients.size()) { + throw new RuntimeException("Index out of bounds for recurseItemTreeAdd, should not happen"); + } + // Loop through NUMBER_OF_INGREDIENTS times. + List current = ingredients.get(index); + Either r; + final ClayBranch branchRight = new ClayBranch(); + for (AbstractMapIngredient obj : current) { + Map> targetMap; + if (obj.isSpecialIngredient()) { + targetMap = branchMap.getSpecialNodes(); + } else { + targetMap = branchMap.getNodes(); + } + + // Either add the recipe or create a branch. + r = targetMap.compute(obj, (k, v) -> { + if (count == ingredients.size() - 1) { + if (v != null) { + if (v.left().isPresent() && v.left().get() == recipe) { + return v; + } else { + if (recipe.getIsCTRecipe()) { + CraftTweakerAPI.logError(String.format("Recipe: %s for Recipe Map %s is a duplicate and was not added"/*ClayCTRecipeHelper.getRecipeAddLine(this, recipe), this.unlocalizedName*/)); + } + if (ConfigHolder.misc.debug) { + GTLog.logger.warn("Recipe: {} for Recipe Map {} is a duplicate and was not added", recipe.toString(), this.unlocalizedName); + } + } + } else { + v = Either.left(recipe); + } + return v; + } else if (v == null) { + v = Either.right(branchRight); + } + return v; + }); + + if (r.right().map(m -> !recurseIngredientTreeAdd(recipe, ingredients, m, (index + 1) % ingredients.size(), count + 1)).orElse(false)) { + current.forEach(i -> { + if (count == ingredients.size() - 1) { + targetMap.remove(obj); + } else { + if (targetMap.get(obj).right().isPresent()) { + ClayBranch branch = targetMap.get(obj).right().get(); + if (branch.isEmptyBranch()) { + targetMap.remove(obj); + } + } + } + }); + return false; + } + } + return true; + } + + protected void buildFromRecipeFluids(List> builder, List fluidInputs) { + for (GTRecipeInput fluidInput : fluidInputs) { + AbstractMapIngredient ingredient; + ingredient = new MapFluidIngredient(fluidInput); + WeakReference cached = fluidIngredientRoot.get(ingredient); + if (cached != null && cached.get() != null) { + builder.add(Collections.singletonList(cached.get())); + } else { + fluidIngredientRoot.put(ingredient, new WeakReference<>(ingredient)); + builder.add(Collections.singletonList(ingredient)); + } + } + } + + protected void buildFromFluidStacks(List> builder, List ingredients) { + for (FluidStack t : ingredients) { + builder.add(Collections.singletonList(new MapFluidIngredient(t))); + } + } + + protected List> fromRecipe(ClayRecipe r) { + List> list = new ObjectArrayList<>((r.getInputs().size()) + r.getFluidInputs().size()); + if (r.getInputs().size() > 0) { + buildFromRecipeItems(list, uniqueIngredientsList(r.getInputs())); + } + if (r.getFluidInputs().size() > 0) { + buildFromRecipeFluids(list, r.getFluidInputs()); + } + return list; + } + + protected void buildFromRecipeItems(List> list, List ingredients) { + for (GTRecipeInput r : ingredients) { + AbstractMapIngredient ingredient; + if (r.isOreDict()) { + hasOreDictedInputs = true; + if (r.hasNBTMatchingCondition()) { + hasNBTMatcherInputs = true; + ingredient = new MapOreDictNBTIngredient(r.getOreDict(), r.getNBTMatcher(), r.getNBTMatchingCondition()); + } else { + ingredient = new MapOreDictIngredient(r.getOreDict()); + } + WeakReference cached = ingredientRoot.get(ingredient); + if (cached != null && cached.get() != null) { + list.add(Collections.singletonList(cached.get())); + } else { + ingredientRoot.put(ingredient, new WeakReference<>(ingredient)); + list.add(Collections.singletonList(ingredient)); + } + } else { + List inner = new ObjectArrayList<>(1); + + for (ItemStack s : r.getInputStacks()) { + if (r.hasNBTMatchingCondition()) { + hasNBTMatcherInputs = true; + ingredient = new MapItemStackNBTIngredient(s, r.getNBTMatcher(), r.getNBTMatchingCondition()); + } else { + ingredient = new MapItemStackIngredient(s); + } + WeakReference cached = ingredientRoot.get(ingredient); + if (cached != null && cached.get() != null) { + inner.add(cached.get()); + } else { + ingredientRoot.put(ingredient, new WeakReference<>(ingredient)); + inner.add(ingredient); + } + } + list.add(inner); + } + } + } + + protected void buildFromItemStacks(List> list, ItemStack[] ingredients) { + AbstractMapIngredient ingredient; + for (ItemStack stack : ingredients) { + int meta = stack.getMetadata(); + NBTTagCompound nbt = stack.getTagCompound(); + + List ls = new ObjectArrayList<>(1); + ls.add(new MapItemStackIngredient(stack, meta, nbt)); + if (hasOreDictedInputs) { + for (int i : OreDictionary.getOreIDs(stack)) { + ingredient = new MapOreDictIngredient(i); + ls.add(ingredient); + if (hasNBTMatcherInputs) { + ingredient = new MapOreDictNBTIngredient(i, nbt); + ls.add(ingredient); + } + } + } + if (hasNBTMatcherInputs) { + ls.add(new MapItemStackNBTIngredient(stack, meta, nbt)); + } + if (ls.size() > 0) { + list.add(ls); + } + } + } + protected ClayRecipeMap setSpecialTexture(int x, int y, int width, int height, TextureArea area) { this.specialTexturePosition = new int[]{x, y, width, height}; this.specialTexture = area; @@ -550,7 +937,7 @@ protected ModularUI.Builder addSpecialTexture(ModularUI.Builder builder) { public Collection getRecipeList() { - return Collections.unmodifiableList(new ArrayList<>(recipeSet)); + return lookup.getRecipes(true).collect(Collectors.toCollection(() -> new TreeSet<>(RECIPE_DURATION_THEN_EU))); } public SoundEvent getSound() { @@ -561,14 +948,8 @@ public SoundEvent getSound() { @Method(modid = GTValues.MODID_CT) @Nullable public ClayCTRecipe ctFindRecipe(long maxVoltage, IItemStack[] itemInputs, ILiquidStack[] fluidInputs, @Optional(valueLong = Integer.MAX_VALUE) int outputFluidTankCapacity) { - List mcItemInputs = itemInputs == null ? Collections.emptyList() : - Arrays.stream(itemInputs) - .map(CraftTweakerMC::getItemStack) - .collect(Collectors.toList()); - List mcFluidInputs = fluidInputs == null ? Collections.emptyList() : - Arrays.stream(fluidInputs) - .map(CraftTweakerMC::getLiquidStack) - .collect(Collectors.toList()); + List mcItemInputs = itemInputs == null ? Collections.emptyList() : Arrays.stream(itemInputs).map(CraftTweakerMC::getItemStack).collect(Collectors.toList()); + List mcFluidInputs = fluidInputs == null ? Collections.emptyList() : Arrays.stream(fluidInputs).map(CraftTweakerMC::getLiquidStack).collect(Collectors.toList()); ClayRecipe backingRecipe = findRecipe(maxVoltage, mcItemInputs, mcFluidInputs, outputFluidTankCapacity, true); return backingRecipe == null ? null : new ClayCTRecipe(this, backingRecipe); } @@ -576,9 +957,7 @@ public ClayCTRecipe ctFindRecipe(long maxVoltage, IItemStack[] itemInputs, ILiqu @ZenGetter("recipes") @Method(modid = GTValues.MODID_CT) public List ccGetRecipeList() { - return getRecipeList().stream() - .map(recipe -> new ClayCTRecipe(this, recipe)) - .collect(Collectors.toList()); + return getRecipeList().stream().map(recipe -> new ClayCTRecipe(this, recipe)).collect(Collectors.toList()); } @ZenGetter("localizedName") @@ -595,6 +974,54 @@ public R recipeBuilder() { return recipeBuilderSample.copy().onBuild(onRecipeBuildAction); } + /** + * Removes a recipe from the map. (recursive part) + * + * @param recipeToRemove the recipe to add. + * @param ingredients list of input ingredients. + * @param branchMap the current branch in the recursion. + */ + private Recipe recurseIngredientTreeRemove(@Nonnull ClayRecipe recipeToRemove, @Nonnull List> ingredients, @Nonnull ClayBranch branchMap, int depth) { + for (List current : ingredients) { + for (AbstractMapIngredient obj : current) { + Map> targetMap; + if (obj.isSpecialIngredient()) { + targetMap = branchMap.getSpecialNodes(); + } else { + targetMap = branchMap.getNodes(); + } + if (ingredients.size() == 0) return null; + Recipe r = removeDive(recipeToRemove, ingredients.subList(1, ingredients.size()), targetMap, obj, depth); + if (r != null) { + if (ingredients.size() == 1) { + targetMap.remove(obj); + } else { + if (targetMap.get(obj).right().isPresent()) { + ClayBranch branch = targetMap.get(obj).right().get(); + if (branch.isEmptyBranch()) { + targetMap.remove(obj); + } + } + } + return r; + } + } + } + return null; + } + + private Recipe removeDive(ClayRecipe recipeToRemove, @Nonnull List> ingredients, Map> targetMap, AbstractMapIngredient obj, int depth) { + Either result = targetMap.get(obj); + if (result != null) { + // Either return recipe or continue branch. + Recipe r = result.map(recipe -> recipe, right -> recurseIngredientTreeRemove(recipeToRemove, ingredients, right, depth + 1)); + if (r == recipeToRemove) { + return r; + } + } + return null; + } + @ZenMethod("recipeBuilder") @Method(modid = GTValues.MODID_CT) public ClayCTRecipeBuilder ctRecipeBuilder() { @@ -644,9 +1071,7 @@ public int getMaxFluidOutputs() { @Override @ZenMethod public String toString() { - return "RecipeMap{" + - "unlocalizedName='" + unlocalizedName + '\'' + - '}'; + return "RecipeMap{" + "unlocalizedName='" + unlocalizedName + '\'' + '}'; } @FunctionalInterface @@ -655,4 +1080,5 @@ public String toString() { public interface IChanceFunction { int chanceFor(int chance, int boostPerTier, int boostTier); } + } diff --git a/src/main/java/clayium/api/recipes/builders/ClaySimpleRecipeBuilder.java b/src/main/java/clayium/api/recipes/builders/ClaySimpleRecipeBuilder.java index 9451a2e..0447fc1 100644 --- a/src/main/java/clayium/api/recipes/builders/ClaySimpleRecipeBuilder.java +++ b/src/main/java/clayium/api/recipes/builders/ClaySimpleRecipeBuilder.java @@ -7,6 +7,7 @@ import gregtech.api.recipes.RecipeBuilder; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.builders.SimpleRecipeBuilder; +import gregtech.api.recipes.recipeproperties.EmptyRecipePropertyStorage; import gregtech.api.util.ValidationResult; public class ClaySimpleRecipeBuilder extends ClayRecipeBuilder { @@ -29,6 +30,7 @@ public ClaySimpleRecipeBuilder copy() { public ValidationResult build() { return ValidationResult.newResult(finalizeAndValidate(), - new ClayRecipe(inputs, outputs, chancedOutputs, fluidInputs, fluidOutputs, duration, EUt, hidden, isCTRecipe, tier)); + new ClayRecipe(inputs, outputs, chancedOutputs, fluidInputs, fluidOutputs, duration, CEt, hidden, isCTRecipe, + recipePropertyStorage == null ? EmptyRecipePropertyStorage.INSTANCE : recipePropertyStorage, tier)); } -} +} \ No newline at end of file diff --git a/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipe.java b/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipe.java index 82ee0b0..0583a9e 100644 --- a/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipe.java +++ b/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipe.java @@ -63,7 +63,7 @@ public List getChancedOutputs() { @ZenGetter("fluidInputs") public List getFluidInputs() { return this.backingRecipe.getFluidInputs().stream() - .map(MCLiquidStack::new) + .map((fi -> new MCLiquidStack(fi.getInputFluidStack()))) .collect(Collectors.toList()); } diff --git a/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipeBuilder.java b/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipeBuilder.java index 1450a51..0061241 100644 --- a/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipeBuilder.java +++ b/src/main/java/clayium/api/recipes/crafttweaker/ClayCTRecipeBuilder.java @@ -7,8 +7,9 @@ import crafttweaker.api.item.IItemStack; import crafttweaker.api.liquid.ILiquidStack; import crafttweaker.api.minecraft.CraftTweakerMC; -import gregtech.api.recipes.CountableIngredient; -import gregtech.api.recipes.ingredients.IntCircuitIngredient; +import crafttweaker.api.oredict.IOreDictEntry; +import gregtech.api.recipes.crafttweaker.CTRecipeBuilder; +import gregtech.api.recipes.ingredients.*; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import stanhebben.zenscript.annotations.ZenClass; @@ -16,6 +17,7 @@ import javax.annotation.Nullable; import java.util.Arrays; +import java.util.Collection; import java.util.stream.Collectors; @ZenClass("mods.clayium.recipe.ClayRecipeBuilder") @@ -48,17 +50,31 @@ public ClayCTRecipeBuilder hidden() { @ZenMethod public ClayCTRecipeBuilder inputs(IIngredient... ingredients) { - this.backingBuilder.inputsIngredients(Arrays.stream(ingredients) - .map(s -> new CountableIngredient(new CraftTweakerIngredientWrapper(s), s.getAmount())) - .collect(Collectors.toList())); + for (IIngredient ingredient : ingredients) { + if (ingredient instanceof IOreDictEntry) { + this.backingBuilder.input( + GTRecipeOreInput.getOrCreate(((IOreDictEntry) ingredient).getName(), ingredient.getAmount())); + } else { + this.backingBuilder.input(GTRecipeItemInput.getOrCreate( + new CTRecipeBuilder.CraftTweakerItemInputWrapper(ingredient), ingredient.getAmount())); + } + } return this; } @ZenMethod public ClayCTRecipeBuilder notConsumable(IIngredient... ingredients) { - this.backingBuilder.inputsIngredients(Arrays.stream(ingredients) - .map(s -> new CountableIngredient(new CraftTweakerIngredientWrapper(s), s.getAmount()).setNonConsumable()) - .collect(Collectors.toList())); + for (IIngredient ingredient : ingredients) { + if (ingredient instanceof IOreDictEntry) { + this.backingBuilder.input( + GTRecipeOreInput.getOrCreate(((IOreDictEntry) ingredient).getName(), ingredient.getAmount()) + .setNonConsumable()); + } else { + this.backingBuilder.input(GTRecipeItemInput.getOrCreate( + new CTRecipeBuilder.CraftTweakerItemInputWrapper(ingredient), ingredient.getAmount()) + .setNonConsumable()); + } + } return this; } @@ -79,9 +95,8 @@ public ClayCTRecipeBuilder circuit(int num) { //note that fluid input predicates are not supported @ZenMethod public ClayCTRecipeBuilder fluidInputs(ILiquidStack... ingredients) { - this.backingBuilder.fluidInputs(Arrays.stream(ingredients) - .map(CraftTweakerMC::getLiquidStack) - .collect(Collectors.toList())); + this.backingBuilder.fluidInputs((Collection) Arrays.stream(ingredients) + .map(CraftTweakerMC::getLiquidStack).map(fluidStack -> GTRecipeFluidInput.getOrCreate(fluidStack, fluidStack.amount)).collect(Collectors.toList())); return this; } @@ -184,19 +199,17 @@ public String toString() { return this.backingBuilder.toString(); } - public static class CraftTweakerIngredientWrapper extends Ingredient { + public static class CraftTweakerItemInputWrapper extends GTRecipeItemInput { private final IIngredient ingredient; - public CraftTweakerIngredientWrapper(IIngredient ingredient) { - super(ingredient.getItems().stream() - .map(CraftTweakerMC::getItemStack) - .toArray(ItemStack[]::new)); + public CraftTweakerItemInputWrapper(IIngredient ingredient) { + super(CraftTweakerMC.getItemStack(ingredient.getItems().get(0))); this.ingredient = ingredient; } @Override - public boolean apply(@Nullable ItemStack itemStack) { + public boolean acceptsStack(@Nullable ItemStack itemStack) { if (itemStack == null) { return false; } diff --git a/src/main/java/clayium/api/recipes/map/ClayBranch.java b/src/main/java/clayium/api/recipes/map/ClayBranch.java new file mode 100644 index 0000000..336811c --- /dev/null +++ b/src/main/java/clayium/api/recipes/map/ClayBranch.java @@ -0,0 +1,56 @@ +package clayium.api.recipes.map; + +import clayium.api.recipes.ClayRecipe; +import gregtech.api.recipes.Recipe; +import gregtech.api.recipes.map.AbstractMapIngredient; +import gregtech.api.recipes.map.Either; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.Map; +import java.util.stream.Stream; + +public class ClayBranch { + // Keys on this have *(should)* unique hashcodes. + private Map> nodes; + // Keys on this have collisions, and must be differentiated by equality. + private Map> specialNodes; + + public Stream getRecipes(boolean filterHidden) { + Stream stream = null; + if (nodes != null) { + stream = nodes.values().stream().flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden))); + } + if (specialNodes != null) { + if (stream == null) { + stream = specialNodes.values().stream().flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden))); + } else { + stream = Stream.concat(stream, specialNodes.values().stream().flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden)))); + } + } + if (stream == null) { + return Stream.empty(); + } + if (filterHidden) { + stream = stream.filter(t -> !t.isHidden()); + } + return stream; + } + + public boolean isEmptyBranch() { + return (nodes == null || nodes.isEmpty()) && (specialNodes == null || specialNodes.isEmpty()); + } + + public Map> getNodes() { + if (nodes == null) { + nodes = new Object2ObjectOpenHashMap<>(2); + } + return nodes; + } + + public Map> getSpecialNodes() { + if (specialNodes == null) { + specialNodes = new Object2ObjectOpenHashMap<>(2); + } + return specialNodes; + } +} diff --git a/src/main/java/clayium/api/util/ClayCTRecipeHelper.java b/src/main/java/clayium/api/util/ClayCTRecipeHelper.java index b876011..91f5fb2 100644 --- a/src/main/java/clayium/api/util/ClayCTRecipeHelper.java +++ b/src/main/java/clayium/api/util/ClayCTRecipeHelper.java @@ -2,14 +2,63 @@ import clayium.api.recipes.ClayRecipe; import clayium.api.recipes.ClayRecipeMap; -import gregtech.api.recipes.CountableIngredient; +import crafttweaker.mc1120.data.NBTConverter; +import gregtech.api.block.machines.MachineItemBlock; +import gregtech.api.items.metaitem.MetaItem; +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.pipenet.block.material.BlockMaterialPipe; +import gregtech.api.recipes.Recipe; +import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ingredients.GTRecipeInput; +import gregtech.api.util.GTLog; +import gregtech.api.util.GTUtility; +import gregtech.common.blocks.BlockCompressed; +import gregtech.common.blocks.BlockFrame; +import net.minecraft.block.Block; +import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; -import static gregtech.api.util.CTRecipeHelper.getCtItemString; +import javax.annotation.Nullable; public class ClayCTRecipeHelper { + @Nullable + public static String getMetaItemId(ItemStack item) { + if (item.getItem() instanceof MetaItem) { + MetaItem metaItem = (MetaItem) item.getItem(); + return metaItem.getItem(item).unlocalizedName; + } + if (item.getItem() instanceof ItemBlock) { + Block block = ((ItemBlock) item.getItem()).getBlock(); + if (item.getItem() instanceof MachineItemBlock) { + MetaTileEntity mte = GTUtility.getMetaTileEntity(item); + if (mte != null) { + return (mte.metaTileEntityId.getNamespace().equals("gregtech") ? mte.metaTileEntityId.getPath() : mte.metaTileEntityId.toString()); + } + } + if (block instanceof BlockCompressed) { + return "block" + ((BlockCompressed) block).getGtMaterial(item.getMetadata()).toCamelCaseString(); + } + if (block instanceof BlockFrame) { + return "frame" + ((BlockFrame) block).getGtMaterial(item.getMetadata()).toCamelCaseString(); + } + if (block instanceof BlockMaterialPipe) { + return ((BlockMaterialPipe) block).getPrefix().name + ((BlockMaterialPipe) block).getItemMaterial(item).toCamelCaseString(); + } + } + return null; + } + + public static String getItemIdFor(ItemStack item) { + String id = getMetaItemId(item); + if (id != null) + return id; + if (item.getItem().getRegistryName() == null) + return "null"; + return item.getItem().getRegistryName().toString(); + } + public static String getRecipeRemoveLine(ClayRecipeMap recipeMap, ClayRecipe recipe) { StringBuilder builder = new StringBuilder(); builder.append(" recipeMap, ClayRecipe if (recipe.getInputs().size() > 0) { builder.append("["); - for (CountableIngredient ci : recipe.getInputs()) { + for (GTRecipeInput ci : recipe.getInputs()) { String ingredient = getCtItemString(ci); if (ingredient != null) builder.append(ingredient); @@ -33,14 +82,14 @@ public static String getRecipeRemoveLine(ClayRecipeMap recipeMap, ClayRecipe if (recipe.getFluidInputs().size() > 0) { builder.append("["); - for (FluidStack fluidStack : recipe.getFluidInputs()) { + for (GTRecipeInput fluidIngredient : recipe.getFluidInputs()) { builder.append(""); - if (fluidStack.amount > 1) { + if (fluidIngredient.getAmount() > 1) { builder.append(" * ") - .append(fluidStack.amount); + .append(fluidIngredient.getAmount()); } builder.append(", "); @@ -56,6 +105,92 @@ public static String getRecipeRemoveLine(ClayRecipeMap recipeMap, ClayRecipe return builder.toString(); } + public static String getRecipeAddLine(ClayRecipeMap recipeMap, ClayRecipe recipe) { + StringBuilder builder = new StringBuilder(); + builder.append(recipeMap.unlocalizedName) + .append(".recipeBuilder()") + .append(".inputs("); + + if (recipe.getInputs().size() > 0) { + builder.append("["); + for (GTRecipeInput ci : recipe.getInputs()) { + String ingredient = getCtItemString(ci); + if (ingredient != null) + builder.append(ingredient); + } + builder.delete(builder.length() - 2, builder.length()) + .append("])"); + } + + if (recipe.getFluidInputs().size() > 0) { + builder.append(".fluidInputs("); + builder.append("["); + for (GTRecipeInput fluidStack : recipe.getFluidInputs()) { + + builder.append(""); + + if (fluidStack.getAmount() > 1) { + builder.append(" * ") + .append(fluidStack.getAmount()); + } + + builder.append(", "); + } + builder.delete(builder.length() - 2, builder.length()) + .append("])"); + } + + if (recipe.getOutputs().size() > 0) { + builder.append(".outputs("); + builder.append("["); + for (ItemStack itemStack : recipe.getOutputs()) { + String itemId = getMetaItemId(itemStack); + if (itemId != null) { + builder.append(""); + } else { + builder.append("<") + .append(itemStack.getItem().getRegistryName().toString()) + .append(":") + .append(itemStack.getItemDamage()) + .append(">"); + } + + if (itemStack.serializeNBT().hasKey("tag")) { + String nbt = NBTConverter.from(itemStack.serializeNBT().getCompoundTag("tag"), false).toString(); + if (nbt.length() > 0) { + builder.append(".withTag(").append(nbt).append(")"); + } + } + } + builder.delete(builder.length() - 2, builder.length()) + .append("])"); + } + + if (recipe.getFluidOutputs().size() > 0) { + builder.append(".fluidOutputs("); + builder.append("["); + for (FluidStack fluidStack : recipe.getFluidOutputs()) { + builder.append(""); + if (fluidStack.amount > 1) { + builder.append(" * ") + .append(fluidStack.amount); + } + } + builder.delete(builder.length() - 2, builder.length()) + .append("])"); + + } + + builder.append("...."); + return builder.toString(); + } + public static String getFirstOutputString(ClayRecipe recipe) { String output = ""; if (!recipe.getOutputs().isEmpty()) { @@ -67,4 +202,49 @@ public static String getFirstOutputString(ClayRecipe recipe) { } return output; } + + public static String getCtItemString(GTRecipeInput recipeInput) { + StringBuilder builder = new StringBuilder(); + ItemStack itemStack = null; + String itemId = null; + for (ItemStack item : recipeInput.getInputStacks()) { + itemId = getMetaItemId(item); + if (itemId != null) { + builder.append(""); + itemStack = item; + break; + } else if (itemStack == null) { + itemStack = item; + } + } + if (itemStack != null) { + if (itemId == null) { + if (itemStack.getItem().getRegistryName() == null) { + GTLog.logger.info("Could not remove recipe {}, because of unregistered Item", builder); + return null; + } + builder.append("<") + .append(itemStack.getItem().getRegistryName().toString()) + .append(":") + .append(itemStack.getItemDamage()) + .append(">"); + } + + if (itemStack.serializeNBT().hasKey("tag")) { + String nbt = NBTConverter.from(itemStack.serializeNBT().getCompoundTag("tag"), false).toString(); + if (nbt.length() > 0) { + builder.append(".withTag(").append(nbt).append(")"); + } + } + } + + if (recipeInput.getAmount() > 1) { + builder.append(" * ") + .append(recipeInput.getAmount()); + } + builder.append(", "); + return builder.toString(); + } } diff --git a/src/main/java/clayium/integration/jei/recipe/ClayRecipeWrapper.java b/src/main/java/clayium/integration/jei/recipe/ClayRecipeWrapper.java index ed0d3ff..18054ff 100644 --- a/src/main/java/clayium/integration/jei/recipe/ClayRecipeWrapper.java +++ b/src/main/java/clayium/integration/jei/recipe/ClayRecipeWrapper.java @@ -6,13 +6,11 @@ import clayium.api.util.ClayUtility; import gregtech.api.GTValues; import gregtech.api.gui.GuiTextures; -import gregtech.api.recipes.CountableIngredient; import gregtech.api.recipes.Recipe.ChanceEntry; +import gregtech.api.recipes.ingredients.GTRecipeInput; import gregtech.api.recipes.recipeproperties.PrimitiveProperty; import gregtech.api.recipes.recipeproperties.RecipeProperty; -import gregtech.api.unification.OreDictUnifier; import gregtech.api.util.ClipboardUtil; -import gregtech.api.util.GTUtility; import gregtech.integration.jei.utils.AdvancedRecipeWrapper; import gregtech.integration.jei.utils.JeiButton; import mezz.jei.api.ingredients.IIngredients; @@ -52,10 +50,9 @@ public void getIngredients(@Nonnull IIngredients ingredients) { // Inputs if (!recipe.getInputs().isEmpty()) { List> matchingInputs = new ArrayList<>(recipe.getInputs().size()); - for (CountableIngredient ci : recipe.getInputs()) { - matchingInputs.add(Arrays.stream(ci.getIngredient().getMatchingStacks()) - .sorted(OreDictUnifier.getItemStackComparator()) - .map(is -> GTUtility.copyAmount(ci.getCount(), is)) + for (GTRecipeInput recipeInput : recipe.getInputs()) { + matchingInputs.add(Arrays.stream(recipeInput.getInputStacks()) + .map(ItemStack::copy) .collect(Collectors.toList())); } ingredients.setInputLists(VanillaTypes.ITEM, matchingInputs); @@ -65,17 +62,9 @@ public void getIngredients(@Nonnull IIngredients ingredients) { if (!recipe.getFluidInputs().isEmpty()) { List matchingFluidInputs = new ArrayList<>(recipe.getFluidInputs().size()); - for (FluidStack fs : recipe.getFluidInputs()) { - if (fs.tag != null && fs.tag.hasKey("nonConsumable")) { - FluidStack fluidCopy = GTUtility.copyAmount(fs.amount, fs); - fluidCopy.tag.removeTag("nonConsumable"); - if (fluidCopy.tag.isEmpty()) { - fluidCopy.tag = null; - } - matchingFluidInputs.add(fluidCopy); - } else { - matchingFluidInputs.add(fs); - } + for (GTRecipeInput fluidInput : recipe.getFluidInputs()) { + FluidStack fluidStack = fluidInput.getInputFluidStack(); + Collections.addAll(matchingFluidInputs, fluidStack); } ingredients.setInputs(VanillaTypes.FLUID, matchingFluidInputs); } @@ -179,7 +168,7 @@ public boolean isNotConsumedItem(int slot) { public boolean isNotConsumedFluid(int slot) { if (slot >= recipe.getFluidInputs().size()) return false; - return recipe.getFluidInputs().get(slot).tag != null && recipe.getFluidInputs().get(slot).tag.hasKey("nonConsumable"); + return recipe.getFluidInputs().get(slot).isNonConsumable(); } private int getPropertyListHeight() { diff --git a/src/main/java/clayium/integration/theoneprobe/provider/ClayElectricContainerInfoProvider.java b/src/main/java/clayium/integration/theoneprobe/provider/ClayElectricContainerInfoProvider.java index fb8f057..d756727 100644 --- a/src/main/java/clayium/integration/theoneprobe/provider/ClayElectricContainerInfoProvider.java +++ b/src/main/java/clayium/integration/theoneprobe/provider/ClayElectricContainerInfoProvider.java @@ -4,43 +4,47 @@ import clayium.api.capability.ClayiumCapabilities; import clayium.api.capability.IClayEnergyContainer; import clayium.api.util.ClayUtility; +import gregtech.api.GTValues; import gregtech.api.capability.GregtechCapabilities; import gregtech.api.capability.GregtechTileCapabilities; import gregtech.api.capability.IEnergyContainer; import gregtech.integration.theoneprobe.provider.CapabilityInfoProvider; import mcjty.theoneprobe.api.ElementAlignment; +import mcjty.theoneprobe.api.IProbeHitData; import mcjty.theoneprobe.api.IProbeInfo; import mcjty.theoneprobe.api.TextStyleClass; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraftforge.common.capabilities.Capability; +import javax.annotation.Nonnull; + public class ClayElectricContainerInfoProvider extends CapabilityInfoProvider { @Override - protected Capability getCapability() { - return ClayiumCapabilities.CAPABILITY_CLAY_ENERGY_CONTAINER; + public String getID() { + return GTValues.MODID + ":energy_container_provider"; } + @Nonnull @Override - public String getID() { - return "clayium:clay_energy_container_provider"; + protected Capability getCapability() { + return ClayiumCapabilities.CAPABILITY_CLAY_ENERGY_CONTAINER; } @Override - protected boolean allowDisplaying(IClayEnergyContainer capability) { + protected boolean allowDisplaying(@Nonnull IClayEnergyContainer capability) { return !capability.isOneProbeHidden(); } @Override - protected void addProbeInfo(IClayEnergyContainer capability, IProbeInfo probeInfo, TileEntity tileEntity, EnumFacing sideHit) { - long energyStored = capability.getEnergyStored(); + protected void addProbeInfo(@Nonnull IClayEnergyContainer capability, @Nonnull IProbeInfo probeInfo, EntityPlayer player, @Nonnull TileEntity tileEntity, @Nonnull IProbeHitData data) { long maxStorage = capability.getEnergyCapacity(); - if (maxStorage == 0) return; //do not add empty max storage progress bar + if (maxStorage == 0) return; // do not add empty max storage progress bar IProbeInfo horizontalPane = probeInfo.horizontal(probeInfo.defaultLayoutStyle().alignment(ElementAlignment.ALIGN_CENTER)); - String additionalSpacing = tileEntity.hasCapability(GregtechTileCapabilities.CAPABILITY_WORKABLE, sideHit) ? " " : ""; - horizontalPane.text(TextStyleClass.INFO + "{*clayium.top.clay_energy_stored*} " + additionalSpacing); - horizontalPane.text(ClayUtility.getCEWithUnit(energyStored) + "/" + ClayUtility.getCEWithUnit(maxStorage) + "CE"); + horizontalPane.text(TextStyleClass.INFO + "{*clayium.top.clay_energy_stored*} "); + horizontalPane.text(ClayUtility.getCEWithUnit(capability.getEnergyStored()) + "/" + ClayUtility.getCEWithUnit(maxStorage) + "CE"); } } diff --git a/src/main/java/clayium/loaders/recipe/ClayCraftingRecipeLoader.java b/src/main/java/clayium/loaders/recipe/ClayCraftingRecipeLoader.java index e24cef0..dd5f771 100644 --- a/src/main/java/clayium/loaders/recipe/ClayCraftingRecipeLoader.java +++ b/src/main/java/clayium/loaders/recipe/ClayCraftingRecipeLoader.java @@ -118,8 +118,6 @@ private static void registerComponentCraftingRecipes() { "NNN", "NRN", "NNN", 'N', CLAY_NEEDLE, 'R', CLAY_RING); ModHandler.addShapedRecipe("clay_bearing", CLAY_BEARING.getStackForm(), "CCC", "CRC", "CCC", 'C', Items.CLAY_BALL, 'R', CLAY_RING); - ModHandler.addShapedRecipe("clay_grinding_head", CLAY_GRINDING_HEAD.getStackForm(), - "NNN", "NRN", "NNN", 'N', CLAY_NEEDLE, 'R', CLAY_RING); ModHandler.addShapedRecipe("clay_spindle", CLAY_SPINDLE.getStackForm(), "RPR", "SBI", "RPR", 'R', CLAY_RING_SMALL, 'P', CLAY_PLATE, 'S', CLAY_STICK, 'B', CLAY_BEARING, 'I', CLAY_RING); ModHandler.addShapedRecipe("clay_cutting_head", CLAY_CUTTING_HEAD.getStackForm(),