Skip to content

Commit

Permalink
Improve JEI Ingredient Tooltip Support
Browse files Browse the repository at this point in the history
  • Loading branch information
IntegerLimit committed Dec 9, 2024
1 parent b2004ae commit 4b7050d
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 14 deletions.
38 changes: 38 additions & 0 deletions src/main/groovy-tests/jeiTests.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,41 @@ addRecipeOutputTooltip(item('minecraft:gold_ingot'), resource('minecraft:gold_in

// Add a translated crafting recipe output tooltip for a specific recipe for a stack (Higher Priority than wild recipe name)
addRecipeOutputTooltip(item('minecraft:iron_ingot'), resource('minecraft:iron_ingot_from_nuggets'), translatable('tooltip.nomilabs.universalnavigator.description'), translatable('tooltip.nomilabs.growth_chamber.description'), translatable('tooltip.nomilabs.dme_sim_chamber.description'))

// Use in a crafting shaped/shapeless builder
crafting.shapedBuilder()
.output(item('minecraft:apple'))
.matrix('AAA', 'AAA', 'ABA')
.key('A', item('minecraft:diamond'))
.key('B', item('minecraft:apple'))
.setOutputTooltip(translatableLiteral('A Very Low Carrot Gold Ingot.').addFormat(TextFormatting.GOLD),
translatable('tooltip.nomilabs.growth_chamber.description'))
.register()

/**
* Recipe Input Tooltips. These are tooltips that appear on CRAFTING TABLE recipes, on a specific registry name and index.
*/

// Similar outside of builder

// In a crafting shaped/shapeless builder
crafting.shapelessBuilder()
.output(item('minecraft:apple'))
.input(item('minecraft:diamond'), item('minecraft:apple'))
// You must specify an index between 0 and 8! This represents the slot number. It goes from left to right, top to bottom.
// E.g. slot 1 = 1st row, 2nd column
.setInputTooltip(1, translatableLiteral('Is a Gold Ingot.').addFormat(TextFormatting.GOLD),
translatable('tooltip.nomilabs.growth_chamber.description'))
.register()

crafting.shapedBuilder()
.output(item('minecraft:apple'))
.matrix('AAA', 'ABA', 'ABA')
.key('A', item('minecraft:diamond'))
.key('B', item('minecraft:apple'))
// You must specify an index between 0 and 8! This represents the slot number. It goes from left to right, top to bottom.
// E.g. slot 4 = 2nd row, 2nd column
.setInputTooltip(4, translatableLiteral('Is a Gold Ingot?').addFormat(TextFormatting.GOLD),
translatable('tooltip.nomilabs.growth_chamber.description'))
.register()

8 changes: 4 additions & 4 deletions src/main/java/com/nomiceu/nomilabs/config/LabsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,12 @@ public static class ModIntegration {
public boolean enableTopAddonsIntegration = true;

@Config.Comment({
"Whether to add a Empty Line between any Crafting Recipe Output Tooltips in JEI.",
"Examples of Crafting Recipe Output Tooltips are `Recipe By <MOD_ID>` and `Recipe ID: <RECIPE_ID>`.",
"Whether to add a Empty Line between any Ingredient Tooltips in JEI.",
"Examples of Ingredient Tooltips are `Recipe By <MOD_ID>`, `Recipe ID: <RECIPE_ID>`, and `Accepts any: <ORE_DICT>`.",
"[default: true]",
})
@Config.LangKey("config.nomilabs.mod_integration.jei_crafting_output_empty_line")
public boolean addJEICraftingOutputEmptyLine = true;
@Config.LangKey("config.nomilabs.mod_integration.jei_ing_empty_line")
public boolean addJEIIngEmptyLine = true;

@Config.Comment({
"Whether to enable Better Questing Fluid Task Fixes.",
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/nomiceu/nomilabs/groovy/GroovyHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ public static void addRecipeOutputTooltip(ItemStack stack, ResourceLocation reci
JEIPlugin.addGroovyRecipeOutputTooltip(stack, recipeName, tooltip);
}

public static void addRecipeInputTooltip(ResourceLocation recipeName, int slotIndex,
LabsTranslate.Translatable... tooltip) {
JEIPlugin.addGroovyRecipeInputTooltip(recipeName, slotIndex, tooltip);
}

/* Hiding Ignore NBT */
public static void hideItemIgnoreNBT(ItemStack stack) {
JEIPlugin.hideItemNBTMatch(stack, (tag) -> true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nomiceu.nomilabs.groovy.mixinhelper;

import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;

import com.nomiceu.nomilabs.integration.jei.JEIPlugin;
import com.nomiceu.nomilabs.util.LabsTranslate;

public class RecipeTooltipAdder {

public static void addTooltips(ResourceLocation rl, ItemStack recipeOutput,
LabsTranslate.Translatable[][] inputTooltip,
LabsTranslate.Translatable[] outputTooltip) {
if (inputTooltip != null) {
for (int i = 0; i < inputTooltip.length; i++) {
var tooltip = inputTooltip[i];
if (tooltip != null)
JEIPlugin.addGroovyRecipeInputTooltip(rl, i, tooltip);
}
}
if (outputTooltip != null)
JEIPlugin.addGroovyRecipeOutputTooltip(recipeOutput, rl, outputTooltip);
}
}
73 changes: 71 additions & 2 deletions src/main/java/com/nomiceu/nomilabs/integration/jei/JEIPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.jetbrains.annotations.NotNull;

import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
Expand All @@ -25,6 +26,7 @@
import com.nomiceu.nomilabs.item.registry.LabsItems;
import com.nomiceu.nomilabs.util.ItemTagMeta;

import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import mezz.jei.api.IJeiRuntime;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.IModRegistry;
Expand All @@ -38,16 +40,22 @@
public class JEIPlugin implements IModPlugin {

private static final ResourceLocation WILDCARD_LOCATION = new ResourceLocation("*", "*");

private static final Map<ItemTagMeta, List<Translatable>> DESCRIPTIONS = new HashMap<>();
private static final Map<ItemTagMeta, List<Translatable>> GROOVY_DESCRIPTIONS = new HashMap<>();

private static final Table<ItemTagMeta, ResourceLocation, List<Translatable>> RECIPE_OUTPUT_TOOLTIPS = HashBasedTable
.create();
private static final Table<ItemTagMeta, ResourceLocation, List<Translatable>> GROOVY_RECIPE_OUTPUT_TOOLTIPS = HashBasedTable
.create();
private static final List<Pair<ItemStack, Function<NBTTagCompound, Boolean>>> IGNORE_NBT_HIDE = new ArrayList<>();

private static Table<ItemTagMeta, ResourceLocation, List<Translatable>> COMPILED_RECIPE_OUTPUT_TOOLTIPS = null;

private static final Map<ResourceLocation, List<Translatable>[]> RECIPE_INPUT_TOOLTIPS = new Object2ObjectOpenHashMap<>();
private static final Map<ResourceLocation, List<Translatable>[]> GROOVY_RECIPE_INPUT_TOOLTIPS = new Object2ObjectOpenHashMap<>();
private static Map<ResourceLocation, List<Translatable>[]> COMPILED_RECIPE_INPUT_TOOLTIPS = null;

private static final List<Pair<ItemStack, Function<NBTTagCompound, Boolean>>> IGNORE_NBT_HIDE = new ArrayList<>();

private static IIngredientRegistry itemRegistry;

@Override
Expand All @@ -73,6 +81,7 @@ public void onRuntimeAvailable(@NotNull IJeiRuntime jeiRuntime) {
Collections.singletonList(new ItemStack(LabsItems.INFO_ITEM)));
}

/* Hiding Helpers */
public static void hideItemNBTMatch(ItemStack itemStack, Function<NBTTagCompound, Boolean> condition) {
IGNORE_NBT_HIDE.add(Pair.of(itemStack, condition));
}
Expand All @@ -92,6 +101,7 @@ public static List<Pair<ItemStack, Function<NBTTagCompound, Boolean>>> getIgnore
return ImmutableList.copyOf(IGNORE_NBT_HIDE);
}

/* Descriptions */
public static void addDescription(@NotNull ItemStack stack, Translatable... description) {
addDescription(DESCRIPTIONS, new ItemTagMeta(stack), (list) -> Collections.addAll(list, description));
}
Expand All @@ -106,6 +116,7 @@ private static void addDescription(Map<ItemTagMeta, List<Translatable>> map,
addToList.accept(map.get(stack));
}

/* Recipe Output Tooltip */
public static void addRecipeOutputTooltip(@NotNull ItemStack stack, Translatable... tooltip) {
addRecipeOutputTooltip(stack, WILDCARD_LOCATION, tooltip);
}
Expand Down Expand Up @@ -158,10 +169,68 @@ public static List<String> getRecipeOutputTooltip(ItemStack stack, ResourceLocat
return new ArrayList<>();
}

/* Recipe Input Tooltip */
public static void addRecipeInputTooltip(@NotNull ResourceLocation recipeName, int slotIndex,
Translatable... tooltip) {
if (slotIndex < 0 || slotIndex > 8)
throw new IllegalArgumentException("Add Recipe Input Tooltip: Slot Index must be between 0 and 8!");

addRecipeInputTooltip(RECIPE_INPUT_TOOLTIPS, recipeName, slotIndex,
(list) -> Collections.addAll(list, tooltip));
}

public static void addGroovyRecipeInputTooltip(@NotNull ResourceLocation recipeName, int slotIndex,
Translatable... tooltip) {
if (slotIndex < 0 || slotIndex > 8) {
GroovyLog.get().error("Add Recipe Input Tooltip: Slot Index must be between 0 and 8!");
return;
}

addRecipeInputTooltip(GROOVY_RECIPE_INPUT_TOOLTIPS, recipeName, slotIndex,
(list) -> Collections.addAll(list, tooltip));
}

private static void addRecipeInputTooltip(Map<ResourceLocation, List<Translatable>[]> map,
@NotNull ResourceLocation recipeName, int slotIndex,
Consumer<List<Translatable>> addToList) {
// noinspection unchecked
var recipeTooltips = map.computeIfAbsent(recipeName, (k) -> (List<Translatable>[]) new List<?>[9]);
var existingTranslations = recipeTooltips[slotIndex];

if (existingTranslations == null) existingTranslations = new ArrayList<>();
addToList.accept(existingTranslations);
recipeTooltips[slotIndex] = existingTranslations;
}

private static void cacheRecipeInputTooltips() {
if (COMPILED_RECIPE_INPUT_TOOLTIPS != null) return;

COMPILED_RECIPE_INPUT_TOOLTIPS = new Object2ObjectOpenHashMap<>(RECIPE_INPUT_TOOLTIPS);
GROOVY_RECIPE_INPUT_TOOLTIPS.forEach((key, value) -> {
for (int i = 0; i < value.length; i++) {
var tooltips = value[i];
if (tooltips == null) continue;
addRecipeInputTooltip(COMPILED_RECIPE_INPUT_TOOLTIPS, key, i, (list) -> list.addAll(tooltips));
}
});
}

public static List<String> getRecipeInputTooltip(ResourceLocation recipeName, int slotIndex) {
cacheRecipeInputTooltips();

if (!COMPILED_RECIPE_INPUT_TOOLTIPS.containsKey(recipeName)) return new ArrayList<>();

var tooltips = COMPILED_RECIPE_INPUT_TOOLTIPS.get(recipeName)[slotIndex];
if (tooltips == null) return new ArrayList<>();
return tooltips.stream().map(Translatable::translate).collect(Collectors.toList());
}

public static void onReload() {
GROOVY_DESCRIPTIONS.clear();
GROOVY_RECIPE_OUTPUT_TOOLTIPS.clear();
GROOVY_RECIPE_INPUT_TOOLTIPS.clear();
IGNORE_NBT_HIDE.clear();
COMPILED_RECIPE_OUTPUT_TOOLTIPS = null;
COMPILED_RECIPE_INPUT_TOOLTIPS = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@
import com.cleanroommc.groovyscript.api.IIngredient;
import com.cleanroommc.groovyscript.compat.vanilla.CraftingRecipeBuilder;
import com.cleanroommc.groovyscript.registry.AbstractCraftingRecipeBuilder;
import com.nomiceu.nomilabs.groovy.mixinhelper.RecipeTooltipAdder;
import com.nomiceu.nomilabs.groovy.mixinhelper.ShapedRecipeClassFunction;
import com.nomiceu.nomilabs.groovy.mixinhelper.ShapedRecipeClassFunctionSimplified;
import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableRecipe;
import com.nomiceu.nomilabs.util.LabsTranslate;

import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;

/**
* Allows for recipes to be 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed
* exactly as set, instead of expanding wildcards and removing duplicates.
* <p>
* Also allows setting custom shaped class, and adding recipe input/output tooltips in JEI.
*/
@Mixin(value = CraftingRecipeBuilder.Shaped.class, remap = false)
@SuppressWarnings("unused")
public abstract class ShapedRecipeBuilderMixin extends AbstractCraftingRecipeBuilder.AbstractShaped<IRecipe> {
Expand All @@ -31,6 +39,12 @@ public abstract class ShapedRecipeBuilderMixin extends AbstractCraftingRecipeBui
@Unique
private boolean labs$isStrict = false;

@Unique
private LabsTranslate.Translatable[][] labs$inputTooltip = null;

@Unique
private LabsTranslate.Translatable[] labs$outputTooltip = null;

/**
* Default Ignored Constructor
*/
Expand Down Expand Up @@ -70,6 +84,25 @@ public CraftingRecipeBuilder.Shaped recipeClassFunction(ShapedRecipeClassFunctio
return (CraftingRecipeBuilder.Shaped) (Object) this;
}

@Unique
public CraftingRecipeBuilder.Shaped setOutputTooltip(LabsTranslate.Translatable... tooltip) {
labs$outputTooltip = tooltip;
return (CraftingRecipeBuilder.Shaped) (Object) this;
}

@Unique
public CraftingRecipeBuilder.Shaped setInputTooltip(int slotIndex, LabsTranslate.Translatable... tooltip) {
if (slotIndex < 0 || slotIndex > 8) {
GroovyLog.get().error("Add Recipe Input Tooltip: Slot Index must be between 0 and 8!");
return (CraftingRecipeBuilder.Shaped) (Object) this;
}

if (labs$inputTooltip == null) labs$inputTooltip = new LabsTranslate.Translatable[9][0];

labs$inputTooltip[slotIndex] = tooltip;
return (CraftingRecipeBuilder.Shaped) (Object) this;
}

@Redirect(method = "register()Lnet/minecraft/item/crafting/IRecipe;",
at = @At(value = "INVOKE",
target = "Lcom/cleanroommc/groovyscript/compat/vanilla/CraftingRecipeBuilder$Shaped;validateShape(Lcom/cleanroommc/groovyscript/api/GroovyLog$Msg;Ljava/util/List;[Ljava/lang/String;Lit/unimi/dsi/fastutil/chars/Char2ObjectOpenHashMap;Lcom/cleanroommc/groovyscript/registry/AbstractCraftingRecipeBuilder$IRecipeCreator;)Ljava/lang/Object;"),
Expand All @@ -95,4 +128,9 @@ public Object registerWithClassFunction2(CraftingRecipeBuilder.Shaped instance,
return validateShape(msg, list, (width1, height1, ingredients) -> labs$recipeClassFunction
.createRecipe(output, width1, height1, ingredients, mirrored, recipeFunction, recipeAction));
}

@Inject(method = "register()Lnet/minecraft/item/crafting/IRecipe;", at = @At("TAIL"))
private void addRecipeTooltips(CallbackInfoReturnable<IRecipe> cir) {
RecipeTooltipAdder.addTooltips(name, output, labs$inputTooltip, labs$outputTooltip);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,39 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.compat.vanilla.CraftingRecipeBuilder;
import com.cleanroommc.groovyscript.registry.AbstractCraftingRecipeBuilder;
import com.nomiceu.nomilabs.groovy.mixinhelper.RecipeTooltipAdder;
import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableRecipe;
import com.nomiceu.nomilabs.util.LabsTranslate;

/**
* Allows for recipes to be 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed
* exactly as set, instead of expanding wildcards and removing duplicates.
* <p>
* Also allows adding recipe input/output tooltips in JEI.
*/
@Mixin(value = CraftingRecipeBuilder.Shapeless.class, remap = false)
@SuppressWarnings("unused")
public class ShapelessRecipeBuilderMixin {
public abstract class ShapelessRecipeBuilderMixin extends AbstractCraftingRecipeBuilder.AbstractShapeless<IRecipe> {

@Unique
private boolean labs$isStrict = false;

@Unique
private LabsTranslate.Translatable[][] labs$inputTooltip = null;

@Unique
private LabsTranslate.Translatable[] labs$outputTooltip = null;

/**
* Default Ignored Constructor
*/
private ShapelessRecipeBuilderMixin(int width, int height) {
super(width, height);
}

/**
* Makes recipes 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed
* exactly as set, instead of expanding wildcards and removing duplicates.
Expand All @@ -32,11 +51,35 @@ public CraftingRecipeBuilder.Shapeless strictJEIHandling() {
return (CraftingRecipeBuilder.Shapeless) (Object) this;
}

@Unique
public CraftingRecipeBuilder.Shapeless setOutputTooltip(LabsTranslate.Translatable... tooltip) {
labs$outputTooltip = tooltip;
return (CraftingRecipeBuilder.Shapeless) (Object) this;
}

@Unique
public CraftingRecipeBuilder.Shapeless setInputTooltip(int slotIndex, LabsTranslate.Translatable... tooltip) {
if (slotIndex < 0 || slotIndex > 8) {
GroovyLog.get().error("Add Recipe Input Tooltip: Slot Index must be between 0 and 8!");
return (CraftingRecipeBuilder.Shapeless) (Object) this;
}

if (labs$inputTooltip == null) labs$inputTooltip = new LabsTranslate.Translatable[9][0];

labs$inputTooltip[slotIndex] = tooltip;
return (CraftingRecipeBuilder.Shapeless) (Object) this;
}

@Inject(method = "register()Lnet/minecraft/item/crafting/IRecipe;", at = @At("RETURN"))
private void setStrict(CallbackInfoReturnable<IRecipe> cir) {
var val = cir.getReturnValue();
if (!(val instanceof StrictableRecipe strict)) return;

if (labs$isStrict) strict.labs$setStrict();
}

@Inject(method = "register()Lnet/minecraft/item/crafting/IRecipe;", at = @At("TAIL"))
private void addRecipeTooltips(CallbackInfoReturnable<IRecipe> cir) {
RecipeTooltipAdder.addTooltips(name, output, labs$inputTooltip, labs$outputTooltip);
}
}
Loading

0 comments on commit 4b7050d

Please sign in to comment.