diff --git a/examples/postInit/generated/jei_generated.groovy b/examples/postInit/generated/jei_generated.groovy index 5f718785d..0b91a8f76 100644 --- a/examples/postInit/generated/jei_generated.groovy +++ b/examples/postInit/generated/jei_generated.groovy @@ -34,9 +34,12 @@ mods.jei.category.hideCategory('minecraft.fuel') // Description Category: // Modify the description of the input items, where the description is a unique JEI tab containing text. +// mods.jei.description.remove(fluid('water')) // mods.jei.description.remove(item('thaumcraft:triple_meat_treat')) -mods.jei.description.add(item('minecraft:gold_ingot'), 'groovyscript.recipe.fluid_recipe') +mods.jei.description.add(fluid('water') * 1000, 'groovyscript.recipe.fluid_recipe') +mods.jei.description.add(item('minecraft:gold_ingot') | item('minecraft:iron_block'), 'groovyscript.recipe.fluid_recipe') +mods.jei.description.add(fluid('lava') * 500, ['very', 'hot', 'fluid']) mods.jei.description.add(item('minecraft:clay'), ['wow', 'this', 'is', 'neat']) // Ingredient Sidebar: diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/Description.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/Description.java index c6e99e961..e28a07fb4 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/Description.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/Description.java @@ -1,60 +1,110 @@ package com.cleanroommc.groovyscript.compat.mods.jei; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import com.cleanroommc.groovyscript.api.GroovyBlacklist; +import com.cleanroommc.groovyscript.api.GroovyLog; import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.api.documentation.annotations.Example; import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; import com.cleanroommc.groovyscript.api.documentation.annotations.RegistryDescription; import com.cleanroommc.groovyscript.core.mixin.jei.IngredientInfoRecipeAccessor; import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; + import mezz.jei.api.IModRegistry; import mezz.jei.api.IRecipeRegistry; -import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.ingredients.IIngredientHelper; +import mezz.jei.api.ingredients.IIngredientRegistry; +import mezz.jei.api.recipe.IIngredientType; import mezz.jei.api.recipe.VanillaRecipeCategoryUid; import mezz.jei.plugins.jei.info.IngredientInfoRecipeCategory; -import net.minecraft.item.ItemStack; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import net.minecraftforge.fluids.FluidStack; @RegistryDescription(category = RegistryDescription.Category.ENTRIES) -public class Description extends VirtualizedRegistry, List>> { +public class Description extends VirtualizedRegistry { + + public static class DescriptionEntry { + List ingredients; + String[] descriptionKeys; + + @SuppressWarnings("unchecked") + DescriptionEntry(@Nonnull List ingredients, @Nullable List descriptionKeys) { + if (ingredients.isEmpty()) { + throw new IllegalArgumentException("JEI Description Entries must not have an empty list of ingredients, got " + ingredients); + } + + // This cast is not exactly correct, but Java/JVM allows it due to type erasure + this.ingredients = (List) ingredients; + + this.descriptionKeys = descriptionKeys == null ? new String[0] : descriptionKeys.toArray(new String[0]); + } + + @Nullable + IIngredientType validateIngredientType(IModRegistry modRegistry) { + Object first = ingredients.get(0); + IIngredientType ingredientType = modRegistry.getIngredientRegistry().getIngredientType(first); + if (ingredientType == null) { + return null; + } + for (Object o : ingredients) { + if (!ingredientType.equals(modRegistry.getIngredientRegistry().getIngredientType(o))) { + return null; + } + } + return ingredientType; + } + } + + private void addIngredientInfo(IModRegistry modRegistry, List ingredients, IIngredientType ingredientType, String[] description) { + // Force Java to pick the correct overload we use + // This is needed because the method has overloads in a way T = Object cannot be substituted + modRegistry.addIngredientInfo(ingredients, ingredientType, description); + } /** * Called by {@link JeiPlugin#register} */ @GroovyBlacklist public void applyAdditions(IModRegistry modRegistry) { - for (Pair, List> entry : this.getScriptedRecipes()) { - modRegistry.addIngredientInfo( - entry.getLeft().stream().flatMap(x -> Stream.of(x.getMatchingStacks())).collect(Collectors.toList()), - // Currently, it is only possible to add VanillaTypes.ITEM. It may be desirable to add the ability to do other types. - VanillaTypes.ITEM, - entry.getRight().toArray(new String[0])); + for (DescriptionEntry entry : this.getScriptedRecipes()) { + IIngredientType ingredientType = entry.validateIngredientType(modRegistry); + if (ingredientType != null) { + addIngredientInfo(modRegistry, entry.ingredients, ingredientType, entry.descriptionKeys); + } else { + GroovyLog.msg("Unable to obtain an ingredient type for items {}", entry.ingredients.toArray()) + .error() + .post(); + } } } /** * Called by {@link JeiPlugin#afterRuntimeAvailable()} */ + @SuppressWarnings("unchecked") @GroovyBlacklist - public void applyRemovals(IRecipeRegistry recipeRegistry) { + public void applyRemovals(IModRegistry modRegistry, IRecipeRegistry recipeRegistry) { + IIngredientRegistry ingredientRegistry = modRegistry.getIngredientRegistry(); IngredientInfoRecipeCategory category = (IngredientInfoRecipeCategory) recipeRegistry.getRecipeCategory(VanillaRecipeCategoryUid.INFORMATION); if (category != null) { recipeRegistry.getRecipeWrappers(category).forEach(wrapper -> { IngredientInfoRecipeAccessor accessor = (IngredientInfoRecipeAccessor) wrapper; - // Currently, it is only possible to remove VanillaTypes.ITEM. It may be desirable to add the ability to do other types. - if (!VanillaTypes.ITEM.equals(accessor.getIngredientType())) return; - - for (Pair, List> entry : this.getBackupRecipes()) { - if (entry.getKey() + for (DescriptionEntry entry : this.getBackupRecipes()) { + IIngredientType ingredientType = entry.validateIngredientType(modRegistry); + IIngredientHelper helper = ingredientRegistry.getIngredientHelper(ingredientType); + if (ingredientType == null || !ingredientType.equals(accessor.getIngredientType())) continue; + if (entry.ingredients .stream() - .anyMatch(x -> accessor.getIngredients().stream().anyMatch(a -> a instanceof ItemStack itemStack && x.test(itemStack)))) { + .anyMatch(x -> accessor.getIngredients().stream().anyMatch(a -> helper.getUniqueId(a).equals(helper.getUniqueId(x))))) { + // the API seems to be broken hence the cast + entry.descriptionKeys = ((List)wrapper.getDescription()).toArray(new String[0]); recipeRegistry.hideRecipe(wrapper, VanillaRecipeCategoryUid.INFORMATION); } } @@ -69,32 +119,80 @@ public void onReload() { } @MethodDescription(type = MethodDescription.Type.ADDITION) - public void add(List target, List description) { - addScripted(Pair.of(target, description)); + public void add(IIngredient[] target, String... description) { + addScripted(new DescriptionEntry( + Stream.of(target).flatMap(x -> Stream.of(x.getMatchingStacks())).collect(Collectors.toList()), + Arrays.asList(description))); } @MethodDescription(type = MethodDescription.Type.ADDITION) - public void add(List target, String... description) { - addScripted(Pair.of(target, Arrays.asList(description))); + public void add(IIngredient[] target, List description) { + add(target, description.toArray(new String[0])); } @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:clay'), ['wow', 'this', 'is', 'neat']")) public void add(IIngredient target, List description) { - addScripted(Pair.of(Collections.singletonList(target), description)); + add(new IIngredient[]{target}, description); } - @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:gold_ingot'), 'groovyscript.recipe.fluid_recipe'")) + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:gold_ingot') | item('minecraft:iron_block'), 'groovyscript.recipe.fluid_recipe'")) public void add(IIngredient target, String... description) { - addScripted(Pair.of(Collections.singletonList(target), Arrays.asList(description))); + add(new IIngredient[]{target}, description); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(Object[] target, String... description) { + addScripted(new DescriptionEntry( + Arrays.asList(target), + Arrays.asList(description))); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(Object[] target, List description) { + add(target, description.toArray(new String[0])); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(Object target, List description) { + add(new Object[]{target}, description); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(Object target, String... description) { + add(new Object[]{target}, description); + } + + // This overload set is the same as Object one. It exists because FluidStack implements IIngredient, but does not have any matching stacks, + // which causes a JEI startup crash when trying to add that description + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(FluidStack[] target, String... description) { + addScripted(new DescriptionEntry( + Arrays.asList(target), + Arrays.asList(description))); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(FluidStack[] target, List description) { + add(target, description.toArray(new String[0])); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("fluid('lava') * 500, ['very', 'hot', 'fluid']")) + public void add(FluidStack target, List description) { + add(new FluidStack[]{target}, description); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("fluid('water') * 1000, 'groovyscript.recipe.fluid_recipe'")) + public void add(FluidStack target, String... description) { + add(new FluidStack[]{target}, description); } @MethodDescription(example = @Example(value = "item('thaumcraft:triple_meat_treat')", commented = true)) - public void remove(List target) { - addBackup(Pair.of(target, null)); + public void remove(IIngredient target) { + addBackup(new DescriptionEntry(Arrays.asList(target.getMatchingStacks()), null)); } - @MethodDescription - public void remove(IIngredient... target) { - addBackup(Pair.of(Arrays.asList(target), null)); + @MethodDescription(example = @Example(value = "fluid('water')", commented = true)) + public void remove(Object target) { + addBackup(new DescriptionEntry(Arrays.asList(target), null)); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/JeiPlugin.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/JeiPlugin.java index dc347a167..7de9372a0 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/JeiPlugin.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/jei/JeiPlugin.java @@ -1,5 +1,9 @@ package com.cleanroommc.groovyscript.compat.mods.jei; +import java.util.Comparator; + +import org.jetbrains.annotations.NotNull; + import com.cleanroommc.groovyscript.api.GroovyBlacklist; import com.cleanroommc.groovyscript.api.GroovyLog; import com.cleanroommc.groovyscript.command.GSCommand; @@ -8,8 +12,14 @@ import com.cleanroommc.groovyscript.compat.mods.ModSupport; import com.cleanroommc.groovyscript.compat.vanilla.ShapedCraftingRecipe; import com.cleanroommc.groovyscript.compat.vanilla.ShapelessCraftingRecipe; + import mezz.jei.Internal; -import mezz.jei.api.*; +import mezz.jei.api.IJeiHelpers; +import mezz.jei.api.IJeiRuntime; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.IModRegistry; +import mezz.jei.api.IRecipeRegistry; +import mezz.jei.api.JEIPlugin; import mezz.jei.api.ingredients.IIngredientHelper; import mezz.jei.api.ingredients.IIngredientRegistry; import mezz.jei.api.ingredients.IIngredientRenderer; @@ -22,9 +32,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.text.TextComponentString; import net.minecraftforge.fluids.FluidStack; -import org.jetbrains.annotations.NotNull; - -import java.util.Comparator; @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") @GroovyBlacklist @@ -47,7 +54,7 @@ public static void afterRegister() { public static void afterRuntimeAvailable() { ModSupport.JEI.get().ingredient.applyChanges(modRegistry.getIngredientRegistry()); ModSupport.JEI.get().category.applyChanges(jeiRuntime.getRecipeRegistry()); - ModSupport.JEI.get().description.applyRemovals(jeiRuntime.getRecipeRegistry()); + ModSupport.JEI.get().description.applyRemovals(modRegistry, jeiRuntime.getRecipeRegistry()); } /**