diff --git a/gradle.properties b/gradle.properties index 91c870d9..db8c2f57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,6 +25,8 @@ mc_version=1.12.2 jei_version=4.8.0.114 #MCMP Stuff mcmp_version=2.3.4_49 +#CT Stuff +ct_version=1.12-4.0.12.323 #Eytra Stuff pdp_version=MC1.12_ver1.1.1 concrete_version=0.3.3-SNAPSHOT diff --git a/project.gradle b/project.gradle index d40d33c8..9f6097c2 100644 --- a/project.gradle +++ b/project.gradle @@ -37,6 +37,11 @@ if (!ext.early) { // MCMP Maven url "http://maven.amadornes.com/" } + maven { + // CraftTweaker2 Maven + name "Jared" + url "http://maven.blamejared.com/" + } } dependencies { @@ -50,5 +55,8 @@ if (!ext.early) { // MCMP deobfCompile "MCMultiPart2:MCMultiPart-exp:${mcmp_version}" + + // CraftTweaker + compile "CraftTweaker2:CraftTweaker2-MC1120-Main:${ct_version}" } } diff --git a/src/main/java/com/elytradev/teckle/common/crafting/AlloyRecipes.java b/src/main/java/com/elytradev/teckle/common/crafting/AlloyRecipes.java index a339ce79..5f7b7f61 100644 --- a/src/main/java/com/elytradev/teckle/common/crafting/AlloyRecipes.java +++ b/src/main/java/com/elytradev/teckle/common/crafting/AlloyRecipes.java @@ -22,6 +22,7 @@ import com.elytradev.teckle.common.item.ItemIngot; import com.elytradev.teckle.common.item.ItemSiliconWafer; import com.google.common.base.Charsets; +import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.io.Resources; import com.google.gson.Gson; @@ -47,9 +48,6 @@ public class AlloyRecipes { private static final AlloyRecipes INSTANCE = new AlloyRecipes(); private List recipes = new ArrayList<>(); - public AlloyRecipes() { - } - public static AlloyRecipes getInstance() { return INSTANCE; } @@ -58,53 +56,79 @@ public List getRecipes() { return Lists.newArrayList(recipes); } - public void init() { + /** + * Unregisters all the currently registered Alloy Recipes. + */ + public void unregisterAll() { recipes.clear(); + } + + /** + * Registers the given recipe for use with the Alloy Furnace. + * + * @param recipe the AlloyRecipe to register. + */ + public void registerRecipe(AlloyRecipe recipe) { + recipes.add(recipe); + } + + /** + * Removes all recipes matching the given predicate. + * + * @param matcher the predicate to check each recipe against. + */ + public void unregisterMatching(Predicate matcher) { + recipes.removeIf(matcher); + } + + public void init() { + unregisterAll(); + AlloyRecipe siliconBouleRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemSiliconBoule), new Tuple<>("sand", 8), new Tuple<>("coal", 8)); - recipes.add(siliconBouleRecipe); + registerRecipe(siliconBouleRecipe); AlloyRecipe redDopedWaferRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemSiliconWafer, 1, ItemSiliconWafer.WaferType.RED.getMetadata()), new Tuple<>("dustRedstone", 4), new Tuple<>(new ItemStack(TeckleObjects.itemSiliconWafer, 1, 0), null)); - recipes.add(redDopedWaferRecipe); + registerRecipe(redDopedWaferRecipe); AlloyRecipe blueDopedWaferRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemSiliconWafer, 1, ItemSiliconWafer.WaferType.BLUE.getMetadata()), new Tuple<>("dustNikolite", 4), new Tuple<>(new ItemStack(TeckleObjects.itemSiliconWafer, 1, 0), null)); - recipes.add(blueDopedWaferRecipe); + registerRecipe(blueDopedWaferRecipe); AlloyRecipe brassIngotRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemIngot, 4, ItemIngot.IngotType.BRASS.getMetadata()), new Tuple<>("ingotTin", 1), new Tuple<>("ingotCopper", 3) ); - recipes.add(brassIngotRecipe); + registerRecipe(brassIngotRecipe); AlloyRecipe redAlloyIngotRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemIngot, 4, ItemIngot.IngotType.RED_ALLOY.getMetadata()), new Tuple<>("ingotCopper", 1), new Tuple<>("dustRedstone", 4) ); - recipes.add(redAlloyIngotRecipe); + registerRecipe(redAlloyIngotRecipe); AlloyRecipe redAlloyIngotRecipeAlt = new AlloyRecipe( new ItemStack(TeckleObjects.itemIngot, 4, ItemIngot.IngotType.RED_ALLOY.getMetadata()), new Tuple<>("ingotIron", 1), new Tuple<>("dustRedstone", 4) ); - recipes.add(redAlloyIngotRecipeAlt); + registerRecipe(redAlloyIngotRecipeAlt); AlloyRecipe blueAlloyIngotRecipe = new AlloyRecipe( new ItemStack(TeckleObjects.itemIngot, 4, ItemIngot.IngotType.BLUE_ALLOY.getMetadata()), new Tuple<>("ingotSilver", 1), new Tuple<>("dustNikolite", 4) ); - recipes.add(blueAlloyIngotRecipe); + registerRecipe(blueAlloyIngotRecipe); // Adds all the vanilla recipes to the alloy furnace. if (TeckleMod.CONFIG.importFurnaceRecipes) @@ -170,7 +194,7 @@ public void init() { Tuple[] inputsArray = new Tuple[inputs.size()]; inputsArray = inputs.toArray(inputsArray); AlloyRecipe loadedRecipe = new AlloyRecipe(outputStack, inputsArray); - recipes.add(loadedRecipe); + registerRecipe(loadedRecipe); } } } @@ -198,10 +222,6 @@ private AlloyRecipe convertFurnaceRecipe(Map.Entry furnace return new AlloyRecipe(furnaceRecipe.getValue(), new Tuple<>(furnaceRecipe.getKey(), null)); } - public void clear() { - recipes.clear(); - } - /** * Data that we deserialize from JSON, uses getters and setters to prevent null results on certain optional vars. */ diff --git a/src/main/java/com/elytradev/teckle/compat/ct/CTAlloyFurnace.java b/src/main/java/com/elytradev/teckle/compat/ct/CTAlloyFurnace.java new file mode 100644 index 00000000..45ea660d --- /dev/null +++ b/src/main/java/com/elytradev/teckle/compat/ct/CTAlloyFurnace.java @@ -0,0 +1,176 @@ +package com.elytradev.teckle.compat.ct; + +import com.elytradev.teckle.common.crafting.AlloyRecipe; +import com.elytradev.teckle.common.crafting.AlloyRecipes; +import crafttweaker.CraftTweakerAPI; +import crafttweaker.IAction; +import crafttweaker.annotations.ZenRegister; +import crafttweaker.api.item.IIngredient; +import crafttweaker.api.item.IItemStack; +import crafttweaker.api.minecraft.CraftTweakerMC; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.Tuple; +import stanhebben.zenscript.annotations.ZenClass; +import stanhebben.zenscript.annotations.ZenMethod; + +/** + * ZenScript class for accessing Alloy Furnace recipes. + * + * Exposes functions to register recipes, remove recipes by output, remove recipes by inputs, and remove all recipes. + */ +@ZenClass("mods.teckle.alloy_furnace") +@ZenRegister +public class CTAlloyFurnace { + + /** + * Creates and registers a recipe for the alloy furnace based on the data provided. + * + * @param output The ItemStack resulting from the recipe. + * @param inputs An array of ingredients required to make the recipe. + */ + @ZenMethod + public static void addRecipe(IItemStack output, IIngredient[] inputs) { + Tuple[] inputTuples = (Tuple[]) new Tuple[inputs.length]; + + if (inputs.length > 9) { + throw new RuntimeException("Alloy Furnace cannot take more than 9 inputs; got " + inputs.length + " inputs instead"); + } + + for (int i = 0; i < inputs.length; i++) { + inputTuples[i] = TeckleCTUtils.convertIngredient(inputs[i]); + } + + ItemStack outputStack = CraftTweakerMC.getItemStack(output); + CraftTweakerAPI.apply(new Add(new AlloyRecipe(outputStack, inputTuples))); + } + + /** + * Created by addRecipe. Registers the internal recipe with the AlloyRecipes registry. + */ + public static class Add implements IAction { + private final AlloyRecipe recipe; + + public Add(AlloyRecipe recipe) { + this.recipe = recipe; + } + + @Override + public void apply() { + AlloyRecipes.getInstance().registerRecipe(recipe); + } + + @Override + public String describe() { + return "Adding " + recipe.getCraftingResult().toString() + " recipe for Alloy Furnace"; + } + } + + /** + * Creates a Remove action that will remove all recipes with the matching output. + * + * @param output The output to be matched against. + * Optionally compares NBT if it is specified. + */ + @ZenMethod + public static void removeRecipe(IItemStack output) { + CraftTweakerAPI.apply(new Remove(CraftTweakerMC.getItemStack(output))); + } + + /** + * Removes every recipe from the AlloyRecipes registry that has the specified output. + */ + public static class Remove implements IAction { + private final ItemStack output; + + public Remove(ItemStack output) { + this.output = output; + } + + @Override + public void apply() { + AlloyRecipes + .getInstance() + .unregisterMatching( + recipe -> TeckleCTUtils.stacksEqual(output, recipe.getCraftingResult(), output.hasTagCompound()) + ); + } + + @Override + public String describe() { + return "Removing Alloy Furnace recipe for " + output.toString(); + } + } + + /** + * Creates a RemoveInput action that will remove all recipes with the matching inputs. + * + * @param inputs The inputs that will be matched against existing recipes. + * NBT will be individually compared if it is specified. + */ + @ZenMethod + public static void removeInputRecipe(IIngredient[] inputs) { + Tuple[] inputTuples = (Tuple[]) new Tuple[inputs.length]; + + if (inputs.length > 9) { + throw new RuntimeException("Alloy Furnace cannot take more than 9 inputs; got " + inputs.length + " inputs instead"); + } + + for (int i = 0; i < inputs.length; i++) { + inputTuples[i] = TeckleCTUtils.convertIngredient(inputs[i]); + } + + NonNullList referenceInputs = (new AlloyRecipe(ItemStack.EMPTY, inputTuples)).getInputs(); + + CraftTweakerAPI.apply(new RemoveInput(referenceInputs)); + } + + /** + * Removes every recipe from the AlloyRecipes registry that has the specified inputs. + */ + public static class RemoveInput implements IAction { + private NonNullList inputs; + + public RemoveInput(NonNullList inputs) { + this.inputs = inputs; + } + + @Override + public void apply() { + AlloyRecipes + .getInstance() + .unregisterMatching( + recipe -> TeckleCTUtils.recipeIngredientsMatch(recipe.getInputs(), inputs) + ); + } + + @Override + public String describe() { + return "Removing matching Alloy Furnace recipes for given inputs: " + inputs; + } + } + + /** + * Creates a RemoveAll action that will remove every recipe from the Alloy Furnace. + * Useful if you want to disable the Alloy Furnace entirely, or want to rewrite all of its recipes. + */ + @ZenMethod + public static void removeAll() { + CraftTweakerAPI.apply(new RemoveAll()); + } + + /** + * Removes all recipes from AlloyRecipes. + */ + public static class RemoveAll implements IAction { + @Override + public void apply() { + AlloyRecipes.getInstance().unregisterAll(); + } + + @Override + public String describe() { + return "Removing all recipes from Alloy Furnace"; + } + } +} diff --git a/src/main/java/com/elytradev/teckle/compat/ct/TeckleCTUtils.java b/src/main/java/com/elytradev/teckle/compat/ct/TeckleCTUtils.java new file mode 100644 index 00000000..cf63a7e5 --- /dev/null +++ b/src/main/java/com/elytradev/teckle/compat/ct/TeckleCTUtils.java @@ -0,0 +1,163 @@ +package com.elytradev.teckle.compat.ct; + +import crafttweaker.CraftTweakerAPI; +import crafttweaker.api.item.IIngredient; +import crafttweaker.api.item.IItemStack; +import crafttweaker.api.item.IngredientStack; +import crafttweaker.api.minecraft.CraftTweakerMC; +import crafttweaker.api.oredict.IOreDictEntry; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.Tuple; +import net.minecraftforge.oredict.OreDictionary; + +import java.util.List; + +/** + * Utilities and helper methods for CTAlloyFurnace. + * Used for comparing and converting between the various ways of representing ingredients. + */ +public class TeckleCTUtils { + /** + * Converts from CraftTweaker ingredients to AlloyRecipe ingredients + * + * @param ingredient The CraftTweaker ingredient to be converted + * @return The converted ingredient, or null for unsupported ingredients + */ + protected static Tuple convertIngredient(IIngredient ingredient) { + // OreDict entry + if (ingredient instanceof IOreDictEntry) { + String ore = ((IOreDictEntry) ingredient).getName(); + + return new Tuple<>(ore, ingredient.getAmount()); + } + + // Literal ItemStack + if (ingredient instanceof IItemStack) { + ItemStack stack = CraftTweakerMC.getItemStack((IItemStack) ingredient); + + return new Tuple<>(stack, stack.getCount()); + } + + // Wrapped OreDict entry with a stack size + if (ingredient instanceof IngredientStack) { + IngredientStack stack = (IngredientStack) ingredient; + + IIngredient internal = (IIngredient) stack.getInternal(); + + if(internal==null) { + CraftTweakerAPI.logWarning("Got null from IngredientStack#getInternal, you most likely have an outdated version of CraftTweaker! Please update to version 4.1.11 or later."); + return new Tuple<>(ItemStack.EMPTY, 0); + } + + if (internal instanceof IOreDictEntry) { + String ore = ((IOreDictEntry) internal).getName(); + + return new Tuple<>(ore, ingredient.getAmount()); + } else { + // This will probably fall through to an invalid ingredient error, since there's no reason to wrap an ItemStack. + return convertIngredient(ingredient); + } + } + + CraftTweakerAPI.logError("Bad ingredient: " + ingredient); + return new Tuple<>(ItemStack.EMPTY, 0); + } + + /** + * Checks if the provided ItemStacks are equal. + * Optionally compares NBT values. + * + * @param stackA The first ItemStack + * @param stackB The second ItemStack + * @param matchNbt Whether NBT data should be checked as well + * @return A boolean indicating whether the stacks are equal. + */ + protected static boolean stacksEqual(ItemStack stackA, ItemStack stackB, boolean matchNbt) { + if (stackA.isEmpty() || stackB.isEmpty() || stackA.getItem() != stackB.getItem()) { + return false; + } + + if (matchNbt && !ItemStack.areItemStackTagsEqual(stackA, stackB)) { + return false; + } + + if (stackA.getHasSubtypes()) { + boolean aWildcard = stackA.getItemDamage() == -1 || stackA.getItemDamage() == OreDictionary.WILDCARD_VALUE; + boolean bWildcard = stackB.getItemDamage() == -1 || stackB.getItemDamage() == OreDictionary.WILDCARD_VALUE; + + if (!(aWildcard || bWildcard)) { + if (stackA.getItemDamage() != stackB.getItemDamage()) { + return false; + } + } + } + + return true; + } + + /** + * Tests if the specified sets of ingredients are equivalent. + * Note: Remember that Alloy recipes are based on Lists, not Sets. + * It will not return true if one list is shuffled relative to the other. + *

+ * Also note the distinction between the first and second inputs. + * The second inputs alone determine whether NBT will be compared based + * on whether they have NBT data. + * + * @param inputs The list of inputs representing the recipe being tested + * @param filter The other list of inputs, generally used for filtering varying inputs in the first slot + * @return Whether the input stack is equivalent to the filter + */ + protected static boolean recipeIngredientsMatch(NonNullList inputs, NonNullList filter) { + if (inputs.size() != filter.size()) { + return false; + } + + for (int i = 0; i < inputs.size(); i++) { + Object inputOriginal = inputs.get(i); + Object inputFilter = filter.get(i); + + // OreDict entry + if (inputOriginal instanceof List && inputFilter instanceof List) { + List listOriginal = (List) inputOriginal; + List listFilter = (List) inputFilter; + + // Make sure the lists are equivalent. + // We can't use List::equals because ItemStack does not have an implementation of equals. + if (listOriginal.size() != listFilter.size()) { + return false; + } + + for (int j = 0; j < listOriginal.size(); j++) { + if (listOriginal.get(j) instanceof ItemStack && listFilter.get(j) instanceof ItemStack) { + + ItemStack itemOriginal = (ItemStack) listOriginal.get(j); + ItemStack itemFilter = (ItemStack) listFilter.get(j); + + if (!stacksEqual(itemOriginal, itemFilter, itemFilter.hasTagCompound())) { + return false; + } + } else { + return false; + } + } + + // pass + continue; + } + + // Literal ItemStack + if (inputOriginal instanceof ItemStack && inputFilter instanceof ItemStack) { + if (stacksEqual((ItemStack) inputOriginal, (ItemStack) inputFilter, ((ItemStack) inputFilter).hasTagCompound())) { + // pass + continue; + } + } + + return false; + } + + return true; + } +}