diff --git a/src/main/java/mezz/jei/plugins/vanilla/VanillaRecipeFactory.java b/src/main/java/mezz/jei/plugins/vanilla/VanillaRecipeFactory.java index dbf716010..112ef31ee 100644 --- a/src/main/java/mezz/jei/plugins/vanilla/VanillaRecipeFactory.java +++ b/src/main/java/mezz/jei/plugins/vanilla/VanillaRecipeFactory.java @@ -1,11 +1,11 @@ package mezz.jei.plugins.vanilla; -import java.util.Collections; import java.util.List; import net.minecraft.item.ItemStack; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import mezz.jei.api.recipe.IRecipeWrapper; import mezz.jei.api.recipe.IVanillaRecipeFactory; import mezz.jei.plugins.vanilla.anvil.AnvilRecipeWrapper; @@ -21,7 +21,7 @@ public IRecipeWrapper createAnvilRecipe(ItemStack leftInput, List rig ErrorUtil.checkNotEmpty(outputs, "outputs"); Preconditions.checkArgument(rightInputs.size() == outputs.size(), "Input and output sizes must match."); - return new AnvilRecipeWrapper(Collections.singletonList(leftInput), rightInputs, outputs); + return new AnvilRecipeWrapper(ImmutableList.of(leftInput), ImmutableList.copyOf(rightInputs), ImmutableList.copyOf(outputs)); } @Override @@ -32,7 +32,7 @@ public IRecipeWrapper createAnvilRecipe(List leftInputs, List getAnvilRecipes(IVanillaRecipeFactory vanillaRecipeFactory, IIngredientRegistry ingredientRegistry) { - List recipes = new ArrayList<>(); Stopwatch sw = Stopwatch.createStarted(); + return Stream.concat( + wrapStream("vanilla repair", () -> getRepairRecipes(vanillaRecipeFactory), sw), + wrapStream("enchantment", () -> getBookEnchantmentRecipes(ingredientRegistry), sw)) + .collect(Collectors.toList()); + } + + private static Stream wrapStream(String name, Supplier> supplier, Stopwatch stopwatch) { + stopwatch.reset(); + stopwatch.start(); try { - getRepairRecipes(recipes, vanillaRecipeFactory); - } catch (RuntimeException e) { - Log.get().error("Failed to create repair recipes.", e); - } - sw.stop(); - Log.get().debug("Registered vanilla repair recipes in {}", sw); - sw.reset(); - sw.start(); - try { - getBookEnchantmentRecipes(recipes, vanillaRecipeFactory, ingredientRegistry); + List data = supplier.get().collect(Collectors.toList()); + Log.get().debug("Registered {} recipes in {}", name, stopwatch); + return data.stream(); } catch (RuntimeException e) { - Log.get().error("Failed to create enchantment recipes.", e); + Log.get().error("Failed to create {} recipes.", name, e); + return Stream.empty(); + } finally { + stopwatch.stop(); } - sw.stop(); - Log.get().debug("Registered enchantment recipes in {}", sw); - return recipes; } - private static void getBookEnchantmentRecipes(List recipes, IVanillaRecipeFactory vanillaRecipeFactory, IIngredientRegistry ingredientRegistry) { - Collection ingredients = ingredientRegistry.getAllIngredients(VanillaTypes.ITEM); - Collection enchantments = ForgeRegistries.ENCHANTMENTS.getValuesCollection(); - for (ItemStack ingredient : ingredients) { - if (ingredient.isItemEnchantable()) { - for (Enchantment enchantment : enchantments) { - if (enchantment.canApply(ingredient)) { - try { - getBookEnchantmentRecipes(recipes, vanillaRecipeFactory, enchantment, ingredient); - } catch (RuntimeException e) { - String ingredientInfo = ErrorUtil.getIngredientInfo(ingredient); - Log.get().error("Failed to register book enchantment recipes for ingredient: {}", ingredientInfo, e); - } - } - } + /* Enchantment recipes */ + + private static final class EnchantmentData { + private final Enchantment enchantment; + // Books per enchantment level + private final List enchantedBooks; + + EnchantmentData(Enchantment enchantment) { + this.enchantment = enchantment; + this.enchantedBooks = enumerateBooks(enchantment); + } + + @SuppressWarnings("UnstableApiUsage") + public List getEnchantedBooks(ItemStack ingredient) { + Item item = ingredient.getItem(); + List list = enchantedBooks.stream() + .filter(enchantedBook -> item.isBookEnchantable(ingredient, enchantedBook)) + .collect(ImmutableList.toImmutableList()); + // avoid using copy of list if it contains the exact same items + return list.size() == enchantedBooks.size() ? enchantedBooks : list; + } + + private boolean canEnchant(ItemStack ingredient) { + try { + return enchantment.canApply(ingredient); + } catch (RuntimeException e) { + String stackInfo = ErrorUtil.getItemStackInfo(ingredient); + Log.get().error("Failed to check if ingredient can be enchanted: {}", stackInfo, e); + return false; } } + + @SuppressWarnings("UnstableApiUsage") + private static List enumerateBooks(Enchantment enchantment) { + return IntStream.rangeClosed(enchantment.getMinLevel(), enchantment.getMaxLevel()) + .mapToObj(level -> ItemEnchantedBook.getEnchantedItemStack(new net.minecraft.enchantment.EnchantmentData(enchantment, level))) + .collect(ImmutableList.toImmutableList()); + } } - private static void getBookEnchantmentRecipes(List recipes, IVanillaRecipeFactory vanillaRecipeFactory, Enchantment enchantment, ItemStack ingredient) { - Item item = ingredient.getItem(); - List perLevelBooks = Lists.newArrayList(); - List perLevelOutputs = Lists.newArrayList(); - for (int level = 1; level <= enchantment.getMaxLevel(); level++) { - Map enchMap = Collections.singletonMap(enchantment, level); - - ItemStack bookEnchant = ENCHANTED_BOOK.copy(); - EnchantmentHelper.setEnchantments(enchMap, bookEnchant); - if (item.isBookEnchantable(ingredient, bookEnchant)) { - perLevelBooks.add(bookEnchant); - - ItemStack withEnchant = ingredient.copy(); - EnchantmentHelper.setEnchantments(enchMap, withEnchant); - perLevelOutputs.add(withEnchant); - } + private static Stream getBookEnchantmentRecipes(IIngredientRegistry ingredientRegistry) { + List enchantmentDatas = ForgeRegistries.ENCHANTMENTS.getValuesCollection().stream() + .map(EnchantmentData::new) + .collect(Collectors.toList()); + Collection ingredients = ingredientRegistry.getAllIngredients(VanillaTypes.ITEM); + return ingredients.stream() + .filter(ItemStack::isItemEnchantable) + .flatMap(ingredient -> getBookEnchantmentRecipes(enchantmentDatas, ingredient)); + } + + private static Stream getBookEnchantmentRecipes(List enchantmentDatas, ItemStack ingredient) { + List ingredientSingletonList = ImmutableList.of(ingredient); + return enchantmentDatas.stream() + .filter(data -> data.canEnchant(ingredient)) + .map(data -> data.getEnchantedBooks(ingredient)) + .filter(enchantedBooks -> !enchantedBooks.isEmpty()) + .map(enchantedBooks -> { + List outputs = getEnchantedIngredients(ingredient, enchantedBooks); + // All lists here are immutable, except outputs which is a transforming list, + // so we call the constructor directly + return new AnvilRecipeWrapper(ingredientSingletonList, enchantedBooks, outputs); + }); + } + + private static List getEnchantedIngredients(ItemStack ingredient, List enchantedBooks) { + return Lists.transform(enchantedBooks, enchantedBook -> getEnchantedIngredient(ingredient, enchantedBook)); + } + + private static ItemStack getEnchantedIngredient(ItemStack ingredient, ItemStack enchantedBook) { + ItemStack enchantedIngredient = ingredient.copy(); + Map enchantments = EnchantmentHelper.getEnchantments(enchantedBook); + EnchantmentHelper.setEnchantments(enchantments, enchantedIngredient); + return enchantedIngredient; + } + + /* Repair recipes */ + + private static class RepairData { + private final ItemStack repairIngredient; + private final List repairables; + + public RepairData(ItemStack repairIngredient, ItemStack... repairables) { + this.repairIngredient = repairIngredient; + this.repairables = Collections.unmodifiableList(Arrays.asList(repairables)); + } + + public ItemStack getRepairStack() { + return repairIngredient; } - if (!perLevelBooks.isEmpty() && !perLevelOutputs.isEmpty()) { - IRecipeWrapper anvilRecipe = vanillaRecipeFactory.createAnvilRecipe(ingredient, perLevelBooks, perLevelOutputs); - recipes.add(anvilRecipe); + + public List getRepairables() { + return repairables; } } - private static void getRepairRecipes(List recipes, IVanillaRecipeFactory vanillaRecipeFactory) { - Map> items = Maps.newHashMap(); - - ItemStack repairWood = new ItemStack(Blocks.PLANKS, 1, OreDictionary.WILDCARD_VALUE); - items.put(repairWood, Lists.newArrayList( - new ItemStack(Items.WOODEN_SWORD), - new ItemStack(Items.WOODEN_PICKAXE), - new ItemStack(Items.WOODEN_AXE), - new ItemStack(Items.WOODEN_SHOVEL), - new ItemStack(Items.WOODEN_HOE), - new ItemStack(Items.SHIELD) - )); - - ItemStack repairStone = new ItemStack(Blocks.COBBLESTONE); - items.put(repairStone, Lists.newArrayList( - new ItemStack(Items.STONE_SWORD), - new ItemStack(Items.STONE_PICKAXE), - new ItemStack(Items.STONE_AXE), - new ItemStack(Items.STONE_SHOVEL), - new ItemStack(Items.STONE_HOE) - )); - - ItemStack repairLeather = new ItemStack(Items.LEATHER); - items.put(repairLeather, Lists.newArrayList( - new ItemStack(Items.LEATHER_HELMET), - new ItemStack(Items.LEATHER_CHESTPLATE), - new ItemStack(Items.LEATHER_LEGGINGS), - new ItemStack(Items.LEATHER_BOOTS), - new ItemStack(Items.ELYTRA) - )); - - ItemStack repairIron = new ItemStack(Items.IRON_INGOT); - items.put(repairIron, Lists.newArrayList( - new ItemStack(Items.IRON_SWORD), - new ItemStack(Items.IRON_PICKAXE), - new ItemStack(Items.IRON_AXE), - new ItemStack(Items.IRON_SHOVEL), - new ItemStack(Items.IRON_HOE), - new ItemStack(Items.IRON_HELMET), - new ItemStack(Items.IRON_CHESTPLATE), - new ItemStack(Items.IRON_LEGGINGS), - new ItemStack(Items.IRON_BOOTS), - new ItemStack(Items.CHAINMAIL_HELMET), - new ItemStack(Items.CHAINMAIL_CHESTPLATE), - new ItemStack(Items.CHAINMAIL_LEGGINGS), - new ItemStack(Items.CHAINMAIL_BOOTS) - )); - - ItemStack repairGold = new ItemStack(Items.GOLD_INGOT); - items.put(repairGold, Lists.newArrayList( - new ItemStack(Items.GOLDEN_SWORD), - new ItemStack(Items.GOLDEN_PICKAXE), - new ItemStack(Items.GOLDEN_AXE), - new ItemStack(Items.GOLDEN_SHOVEL), - new ItemStack(Items.GOLDEN_HOE), - new ItemStack(Items.GOLDEN_HELMET), - new ItemStack(Items.GOLDEN_CHESTPLATE), - new ItemStack(Items.GOLDEN_LEGGINGS), - new ItemStack(Items.GOLDEN_BOOTS) - )); - - ItemStack repairDiamond = new ItemStack(Items.DIAMOND); - items.put(repairDiamond, Lists.newArrayList( - new ItemStack(Items.DIAMOND_SWORD), - new ItemStack(Items.DIAMOND_PICKAXE), - new ItemStack(Items.DIAMOND_AXE), - new ItemStack(Items.DIAMOND_SHOVEL), - new ItemStack(Items.DIAMOND_HOE), - new ItemStack(Items.DIAMOND_HELMET), - new ItemStack(Items.DIAMOND_CHESTPLATE), - new ItemStack(Items.DIAMOND_LEGGINGS), - new ItemStack(Items.DIAMOND_BOOTS) - )); - - for (Map.Entry> entry : items.entrySet()) { - - ItemStack repairMaterial = entry.getKey(); - - for (ItemStack ingredient : entry.getValue()) { - - ItemStack damaged1 = ingredient.copy(); - damaged1.setItemDamage(damaged1.getMaxDamage()); - ItemStack damaged2 = ingredient.copy(); - damaged2.setItemDamage(damaged2.getMaxDamage() * 3 / 4); - ItemStack damaged3 = ingredient.copy(); - damaged3.setItemDamage(damaged3.getMaxDamage() * 2 / 4); - - IRecipeWrapper repairWithMaterial = vanillaRecipeFactory.createAnvilRecipe(damaged1, Collections.singletonList(repairMaterial), Collections.singletonList(damaged2)); - IRecipeWrapper repairWithSame = vanillaRecipeFactory.createAnvilRecipe(damaged2, Collections.singletonList(damaged2), Collections.singletonList(damaged3)); - recipes.add(repairWithMaterial); - recipes.add(repairWithSame); - } + private static Stream getRepairData() { + return Stream.of( + new RepairData(Item.ToolMaterial.WOOD.getRepairItemStack(), + new ItemStack(Items.WOODEN_SWORD), + new ItemStack(Items.WOODEN_PICKAXE), + new ItemStack(Items.WOODEN_AXE), + new ItemStack(Items.WOODEN_SHOVEL), + new ItemStack(Items.WOODEN_HOE) + ), + new RepairData(new ItemStack(Blocks.PLANKS, 1, OreDictionary.WILDCARD_VALUE), + new ItemStack(Items.SHIELD) + ), + new RepairData(Item.ToolMaterial.STONE.getRepairItemStack(), + new ItemStack(Items.STONE_SWORD), + new ItemStack(Items.STONE_PICKAXE), + new ItemStack(Items.STONE_AXE), + new ItemStack(Items.STONE_SHOVEL), + new ItemStack(Items.STONE_HOE) + ), + new RepairData(ItemArmor.ArmorMaterial.LEATHER.getRepairItemStack(), + new ItemStack(Items.LEATHER_HELMET), + new ItemStack(Items.LEATHER_CHESTPLATE), + new ItemStack(Items.LEATHER_LEGGINGS), + new ItemStack(Items.LEATHER_BOOTS) + ), + new RepairData(new ItemStack(Items.LEATHER), + new ItemStack(Items.ELYTRA) + ), + new RepairData(Item.ToolMaterial.IRON.getRepairItemStack(), + new ItemStack(Items.IRON_SWORD), + new ItemStack(Items.IRON_PICKAXE), + new ItemStack(Items.IRON_AXE), + new ItemStack(Items.IRON_SHOVEL), + new ItemStack(Items.IRON_HOE) + ), + new RepairData(ItemArmor.ArmorMaterial.IRON.getRepairItemStack(), + new ItemStack(Items.IRON_HELMET), + new ItemStack(Items.IRON_CHESTPLATE), + new ItemStack(Items.IRON_LEGGINGS), + new ItemStack(Items.IRON_BOOTS) + ), + new RepairData(ItemArmor.ArmorMaterial.CHAIN.getRepairItemStack(), + new ItemStack(Items.CHAINMAIL_HELMET), + new ItemStack(Items.CHAINMAIL_CHESTPLATE), + new ItemStack(Items.CHAINMAIL_LEGGINGS), + new ItemStack(Items.CHAINMAIL_BOOTS) + ), + new RepairData(Item.ToolMaterial.GOLD.getRepairItemStack(), + new ItemStack(Items.GOLDEN_SWORD), + new ItemStack(Items.GOLDEN_PICKAXE), + new ItemStack(Items.GOLDEN_AXE), + new ItemStack(Items.GOLDEN_SHOVEL), + new ItemStack(Items.GOLDEN_HOE) + ), + new RepairData(ItemArmor.ArmorMaterial.GOLD.getRepairItemStack(), + new ItemStack(Items.GOLDEN_HELMET), + new ItemStack(Items.GOLDEN_CHESTPLATE), + new ItemStack(Items.GOLDEN_LEGGINGS), + new ItemStack(Items.GOLDEN_BOOTS) + ), + new RepairData(Item.ToolMaterial.DIAMOND.getRepairItemStack(), + new ItemStack(Items.DIAMOND_SWORD), + new ItemStack(Items.DIAMOND_PICKAXE), + new ItemStack(Items.DIAMOND_AXE), + new ItemStack(Items.DIAMOND_SHOVEL), + new ItemStack(Items.DIAMOND_HOE) + ), + new RepairData(ItemArmor.ArmorMaterial.DIAMOND.getRepairItemStack(), + new ItemStack(Items.DIAMOND_HELMET), + new ItemStack(Items.DIAMOND_CHESTPLATE), + new ItemStack(Items.DIAMOND_LEGGINGS), + new ItemStack(Items.DIAMOND_BOOTS) + ) + ); + } + + private static Stream getRepairRecipes(IVanillaRecipeFactory vanillaRecipeFactory) { + return getRepairData().flatMap(repairData -> getRepairRecipes(repairData, vanillaRecipeFactory)); + } + + private static Stream getRepairRecipes(RepairData repairData, IVanillaRecipeFactory vanillaRecipeFactory) { + List repairStackInput = ImmutableList.of(repairData.getRepairStack()); + List repairables = repairData.getRepairables(); + Stream.Builder recipes = Stream.builder(); + + for (ItemStack repairable : repairables) { + ItemStack damagedThreeQuarters = repairable.copy(); + damagedThreeQuarters.setItemDamage(damagedThreeQuarters.getMaxDamage() * 3 / 4); + ItemStack damagedHalf = repairable.copy(); + damagedHalf.setItemDamage(damagedHalf.getMaxDamage() / 2); + + List damagedThreeQuartersSingletonList = ImmutableList.of(damagedThreeQuarters); + IRecipeWrapper repairWithSame = vanillaRecipeFactory.createAnvilRecipe( + damagedThreeQuartersSingletonList, + damagedThreeQuartersSingletonList, + ImmutableList.of(damagedHalf) + ); + recipes.add(repairWithSame); + + ItemStack damagedFully = repairable.copy(); + damagedFully.setItemDamage(damagedFully.getMaxDamage()); + IRecipeWrapper repairWithMaterial = vanillaRecipeFactory.createAnvilRecipe( + ImmutableList.of(damagedFully), + repairStackInput, + damagedThreeQuartersSingletonList + ); + recipes.add(repairWithMaterial); } + + return recipes.build(); } public static int findLevelsCost(ItemStack leftStack, ItemStack rightStack) { diff --git a/src/main/java/mezz/jei/plugins/vanilla/anvil/AnvilRecipeWrapper.java b/src/main/java/mezz/jei/plugins/vanilla/anvil/AnvilRecipeWrapper.java index 986c08062..0e2c1cd51 100644 --- a/src/main/java/mezz/jei/plugins/vanilla/anvil/AnvilRecipeWrapper.java +++ b/src/main/java/mezz/jei/plugins/vanilla/anvil/AnvilRecipeWrapper.java @@ -1,6 +1,5 @@ package mezz.jei.plugins.vanilla.anvil; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -17,12 +16,14 @@ import mezz.jei.api.recipe.IRecipeWrapper; public class AnvilRecipeWrapper implements IRecipeWrapper { - private final List> inputs; - private final List> output; - - public AnvilRecipeWrapper(List leftInput, List rightInputs, List outputs) { - this.inputs = ImmutableList.of(leftInput, rightInputs); - this.output = Collections.singletonList(outputs); + private final List leftInputs; + private final List rightInputs; + private final List output; + + public AnvilRecipeWrapper(List leftInputs, List rightInputs, List outputs) { + this.leftInputs = leftInputs; + this.rightInputs = rightInputs; + this.output = outputs; } @Override @@ -44,8 +45,8 @@ public void drawInfo(Minecraft minecraft, int recipeWidth, int recipeHeight, int ItemStack lastRightStack = data.getLastRightStack(); int lastCost = data.getLastCost(); if (lastLeftStack == null || lastRightStack == null - || !ItemStack.areItemStacksEqual(lastLeftStack, newLeftStack) - || !ItemStack.areItemStacksEqual(lastRightStack, newRightStack)) { + || !ItemStack.areItemStacksEqual(lastLeftStack, newLeftStack) + || !ItemStack.areItemStacksEqual(lastRightStack, newRightStack)) { lastCost = AnvilRecipeMaker.findLevelsCost(newLeftStack, newRightStack); data.setLast(newLeftStack, newRightStack, lastCost); } @@ -57,8 +58,8 @@ public void drawInfo(Minecraft minecraft, int recipeWidth, int recipeHeight, int int mainColor = 0xFF80FF20; EntityPlayerSP player = minecraft.player; if (player != null && - (lastCost >= 40 || lastCost > player.experienceLevel) && - !player.capabilities.isCreativeMode) { + (lastCost >= 40 || lastCost > player.experienceLevel) && + !player.capabilities.isCreativeMode) { // Show red if the player doesn't have enough levels mainColor = 0xFFFF6060; } @@ -87,7 +88,7 @@ private void drawRepairCost(Minecraft minecraft, String text, int mainColor, int @Override public void getIngredients(IIngredients ingredients) { - ingredients.setInputLists(VanillaTypes.ITEM, inputs); - ingredients.setOutputLists(VanillaTypes.ITEM, output); + ingredients.setInputLists(VanillaTypes.ITEM, ImmutableList.of(leftInputs, rightInputs)); + ingredients.setOutputLists(VanillaTypes.ITEM, ImmutableList.of(output)); } } diff --git a/src/main/java/mezz/jei/startup/ModRegistry.java b/src/main/java/mezz/jei/startup/ModRegistry.java index 725673891..321aea686 100644 --- a/src/main/java/mezz/jei/startup/ModRegistry.java +++ b/src/main/java/mezz/jei/startup/ModRegistry.java @@ -295,7 +295,7 @@ public void addAnvilRecipe(ItemStack leftInput, List rightInputs, Lis ErrorUtil.checkNotEmpty(outputs, "outputs"); Preconditions.checkArgument(rightInputs.size() == outputs.size(), "Input and output sizes must match."); - AnvilRecipeWrapper anvilRecipeWrapper = new AnvilRecipeWrapper(Collections.singletonList(leftInput), rightInputs, outputs); + AnvilRecipeWrapper anvilRecipeWrapper = new AnvilRecipeWrapper(ImmutableList.of(leftInput), rightInputs, outputs); addRecipes(Collections.singletonList(anvilRecipeWrapper), VanillaRecipeCategoryUid.ANVIL); }