Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions 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(fluid('water'), 'groovyscript.recipe.fluid_recipe')
mods.jei.description.add(item('minecraft:gold_ingot'), 'groovyscript.recipe.fluid_recipe')
mods.jei.description.add(fluid('lava'), ['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,105 @@
package com.cleanroommc.groovyscript.compat.mods.jei;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
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.helper.ingredient.ItemsIngredient;
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.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<?>> {

static class DescriptionEntry<T> {
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.

think this needs public

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.

As of right now it's only used within the class, any reason why?

T representative;
List<T> ingredients;
IIngredientType<T> ingredientType;
String[] descriptionKeys;

DescriptionEntry(@Nonnull List<T> ingredients, @Nonnull IIngredientType<T> ingredientType, @Nullable List<String> descriptionKeys) {
if (ingredients.isEmpty()) {
throw new IllegalArgumentException("JEI Description Entries must not have an empty list of ingredients, got " + ingredients);
}
Objects.requireNonNull(ingredientType);

this.ingredients = ingredients;
this.ingredientType = ingredientType;
this.representative = this.ingredients.get(0);
Objects.requireNonNull(representative);

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

/**
* Called by {@link JeiPlugin#register}
*/
@SuppressWarnings("unchecked")
@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()) {
if (entry.representative instanceof ItemStack) {
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.

would suggest ensuring everything is the same IIngredientRegistry#getIngredientType + registering via that (casting as needed. shouldnt need an instanceof then

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.

This is not possible due to a Java quirk, that I have already outlined in the Discord channel. The JEI API exposes an overloaded function like this:

<T> void addIngredientInfo(T ingredient, IIngredientType<T> ingredientType, String... descriptionKeys);
<T> void addIngredientInfo(List<T> ingredients, IIngredientType<T> ingredientType, String... descriptionKeys);

If T := Object is substituted (or T being an existential type, which eventually reduces to Object), then these two methods are ambiguous, so T must be a concrete type, hence this is necessary.

modRegistry.addIngredientInfo((List<ItemStack>) entry.ingredients, (IIngredientType<ItemStack>) entry.ingredientType, entry.descriptionKeys);
} else if (entry.representative instanceof FluidStack) {
modRegistry.addIngredientInfo((List<FluidStack>) entry.ingredients, (IIngredientType<FluidStack>) entry.ingredientType, entry.descriptionKeys);
} else {
GroovyLog.msg("Got a Description Entry of an invalid type, this is probably a bug in Groovyscript").error().post();
}
}
}

private boolean compareObjects(Object inEntry, Object inRegistry) {
if (inEntry instanceof ItemStack ieStack) {
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.

since this happens only in applyRemovals, we can compare using IIngredientHelper#getUniqueId - perhaps IngredientUtil#equal? although i think that might be intended for internal logic.

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.

The problem is sometimes you need to remove description from an item with a specific NBT, which checking only the ID doesn't. I wanted to keep the old logic in regards to this. Though I don't really like how this turned out, yeah

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.

See my comment on applyAdditions

return inRegistry instanceof ItemStack irStack && new ItemsIngredient(ieStack).test(irStack);
} else if (inEntry instanceof FluidStack ieStack) {
return inRegistry instanceof FluidStack irStack && ieStack.getFluid().equals(irStack.getFluid());
} else {
GroovyLog.msg("Got a Description Entry of type {} which is not supported, this is probably a bug in Groovyscript", inEntry.getClass().getName())
.error()
.post();
return false;
}
}

/**
* Called by {@link JeiPlugin#afterRuntimeAvailable()}
*/
@SuppressWarnings("unchecked")
@GroovyBlacklist
public void applyRemovals(IRecipeRegistry recipeRegistry) {
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()) {
if (!entry.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 -> compareObjects(x, a)))) {
// 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 @@ -69,32 +114,58 @@ public void onReload() {
}

@MethodDescription(type = MethodDescription.Type.ADDITION)
public void add(List<IIngredient> target, List<String> description) {
addScripted(Pair.of(target, description));
public void add(IIngredient[] target, String... description) {
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.

maybe do Object[] target + get the IIngredientRegistry#getIngredientType later on + check that everything has the same ingredient type. wouldnt use IIngredient or FluidStack anymore

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.

Hmm, we could do that I guess, that would be much better in terms of future flexibility too

addScripted(new DescriptionEntry<>(
Stream.of(target).flatMap(x -> Stream.of(x.getMatchingStacks())).collect(Collectors.toList()),
VanillaTypes.ITEM,
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(IIngredient[] 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));
add(new IIngredient[]{target}, 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)));
add(new IIngredient[]{target}, description);
}

@MethodDescription(type = MethodDescription.Type.ADDITION)
public void add(FluidStack[] target, String... description) {
addScripted(new DescriptionEntry<>(
Arrays.asList(target),
VanillaTypes.FLUID,
Arrays.asList(description)));
}

@MethodDescription(type = MethodDescription.Type.ADDITION)
public void add(FluidStack[] target, List<String> 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<String> 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<IIngredient> target) {
addBackup(Pair.of(target, null));
public void remove(IIngredient target) {
addBackup(new DescriptionEntry<>(Arrays.asList(target.getMatchingStacks()), VanillaTypes.ITEM, 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(FluidStack target) {
addBackup(new DescriptionEntry<>(Arrays.asList(target), VanillaTypes.FLUID, null));
}
}