Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/postInit/generated/jei_generated.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,111 @@
package com.cleanroommc.groovyscript.compat.mods.jei;

import java.util.ArrayList;
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<Pair<List<IIngredient>, List<String>>> {
public class Description extends VirtualizedRegistry<Description.DescriptionEntry> {

public static class DescriptionEntry {
List<Object> ingredients;
String[] descriptionKeys;

@SuppressWarnings("unchecked")
DescriptionEntry(@Nonnull List<?> ingredients, @Nullable List<String> 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<Object>) ingredients;

this.descriptionKeys = descriptionKeys == null ? new String[0] : descriptionKeys.toArray(new String[0]);
}

@Nullable
IIngredientType<Object> validateIngredientType(IModRegistry modRegistry) {
Object first = ingredients.get(0);
IIngredientType<Object> 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 <T> void addIngredientInfo(IModRegistry modRegistry, List<T> ingredients, IIngredientType<T> 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<IIngredient>, List<String>> 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<Object> 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<IIngredient>, List<String>> entry : this.getBackupRecipes()) {
if (entry.getKey()
for (DescriptionEntry entry : this.getBackupRecipes()) {
IIngredientType<Object> ingredientType = entry.validateIngredientType(modRegistry);
IIngredientHelper<Object> 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<String>)wrapper.getDescription()).toArray(new String[0]);
recipeRegistry.hideRecipe(wrapper, VanillaRecipeCategoryUid.INFORMATION);
}
}
Expand All @@ -68,33 +119,51 @@ public void onReload() {
removeScripted();
}

private @Nullable List<Object> toStacks(List<Object> target) {
if (target == null || target.isEmpty()) {
GroovyLog.msg("The ingredient list cannot be empty")
.error()
.post();
return null;
}

return target.stream()
.<Object> flatMap(x ->
x instanceof IIngredient && !(x instanceof FluidStack)
? Stream.of(((IIngredient)x).getMatchingStacks()) : Stream.of(x))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in what situation would this be called? if its item('whatever') then its already an itemstack - no need to convert - and if its ore('whatever') it should just be rejected. this code means a gas('whatever') would be converted into an ItemStack stream (it is an IIngredient).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to reject ore(whatever)?

I didn't think of the gas implications though, that one is fair. Maybe we add a new method to IIngredient that determines whether this ingredient handles items or not

Copy link
Copy Markdown
Collaborator

@WaitingIdly WaitingIdly Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to reject ore(whatever)?

because jei uses IngredientTypes, only things that are of an IngredientType is relevant. ore('whatever') doesnt create one of these

.collect(Collectors.toList());
}

@MethodDescription(type = MethodDescription.Type.ADDITION)
public void add(List<IIngredient> target, List<String> description) {
addScripted(Pair.of(target, description));
public void add(List<Object> target, String... description) {
target = toStacks(target);
if (target != null) {
addScripted(new DescriptionEntry(target, Arrays.asList(description)));
}
}

@MethodDescription(type = MethodDescription.Type.ADDITION)
public void add(List<IIngredient> target, String... description) {
addScripted(Pair.of(target, Arrays.asList(description)));
public void add(List<Object> target, List<String> 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<String> description) {
addScripted(Pair.of(Collections.singletonList(target), description));
public void add(Object target, List<String> description) {
List<Object> objects = new ArrayList<>();
objects.add(target);
add(objects, description);
}

@MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:gold_ingot'), 'groovyscript.recipe.fluid_recipe'"))
public void add(IIngredient target, String... description) {
addScripted(Pair.of(Collections.singletonList(target), Arrays.asList(description)));
@MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:gold_ingot') | item('minecraft:iron_block'), 'groovyscript.recipe.fluid_recipe'"))
public void add(Object target, String... description) {
List<Object> objects = new ArrayList<>();
objects.add(target);
add(objects, description);
}

@MethodDescription(example = @Example(value = "item('thaumcraft:triple_meat_treat')", commented = true))
public void remove(List<IIngredient> target) {
addBackup(Pair.of(target, null));
}

@MethodDescription
public void remove(IIngredient... target) {
addBackup(Pair.of(Arrays.asList(target), null));
public void remove(Object target) {
List<Object> targets = toStacks(Arrays.asList(target));
addBackup(new DescriptionEntry(targets, null));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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());
}

/**
Expand Down