jei compat

This commit is contained in:
Lorenz Wrobel 2026-05-23 01:25:40 +02:00
parent 61c7a99e94
commit 81c1dcc49f
13 changed files with 314 additions and 96 deletions

View File

@ -79,11 +79,11 @@ public class CompatUtil {
return materials; return materials;
} }
public static <C extends RecipeInput, R extends Recipe<C>, T> List<T> collectAllRecipes(RecipeManager recipeManager, RecipeType<R> recipeType, Function<R, T> mapper) { public static <C extends RecipeInput, R extends Recipe<C>, T> List<T> collectAllRecipes(RecipeManager recipeManager, RecipeType<R> recipeType, Function<RecipeHolder<R>, T> mapper) {
var byType = recipeManager.byType(recipeType); var byType = recipeManager.byType(recipeType);
List<T> recipes = new ObjectArrayList<>(byType.size()); List<T> recipes = new ObjectArrayList<>(byType.size());
for (RecipeHolder<R> value : byType) { for (RecipeHolder<R> value : byType) {
recipes.add(mapper.apply(value.value())); recipes.add(mapper.apply(value));
} }
return recipes; return recipes;
} }

View File

@ -22,10 +22,12 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
@ -34,33 +36,45 @@ import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe; import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.registry.EItems; import thedarkcolour.exdeorum.registry.EItems;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Since no JEI code is used here, this can be reused for REI // Since no JEI code is used here, this can be reused for REI
public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List<Result> results) { public record XeiSieveRecipe(ResourceLocation id, Ingredient ingredient, ItemStack mesh, List<Result> results) {
public static final MutableInt SIEVE_ROWS = new MutableInt(0); public static final MutableInt SIEVE_ROWS = new MutableInt(0);
public static final MutableInt COMPRESSED_SIEVE_ROWS = new MutableInt(0); public static final MutableInt COMPRESSED_SIEVE_ROWS = new MutableInt(0);
public static ImmutableList<XeiSieveRecipe> getAllRecipesGrouped(RecipeType<? extends SieveRecipe> recipeType, MutableInt maxRows) { public static <T extends SieveRecipe> ImmutableList<XeiSieveRecipe> getAllRecipesGrouped(RecipeType<T> recipeType, MutableInt maxRows) {
int maxSieveRows = 1; int maxSieveRows = 1;
var recipes = CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), recipeType, Function.identity()); var recipeHolders = CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), recipeType, Function.identity());
Multimap<Ingredient, SieveRecipe> ingredientGrouper = ArrayListMultimap.create(); var recipeTypeKey = Objects.requireNonNull(BuiltInRegistries.RECIPE_TYPE.getKey(recipeType));
Multimap<Ingredient, RecipeHolder<T>> ingredientGrouper = ArrayListMultimap.create();
for (int i = 0; i < recipes.size(); i++) { for (int i = 0; i < recipeHolders.size(); i++) {
var recipe = recipes.get(i); var holder = recipeHolders.get(i);
var recipe = holder.value();
ingredientGrouper.put(recipe.ingredient(), recipe); ingredientGrouper.put(recipe.ingredient(), holder);
for (int j = i + 1; j < recipes.size(); j++) { for (int j = i + 1; j < recipeHolders.size(); j++) {
var other = recipes.get(j); var otherHolder = recipeHolders.get(j);
var other = otherHolder.value();
if (RecipeUtil.areIngredientsEqual(recipe.ingredient(), other.ingredient())) { if (RecipeUtil.areIngredientsEqual(recipe.ingredient(), other.ingredient())) {
ingredientGrouper.put(recipe.ingredient(), other); ingredientGrouper.put(recipe.ingredient(), otherHolder);
recipes.remove(other); recipeHolders.remove(otherHolder);
j--; j--;
} }
} }
@ -74,13 +88,23 @@ public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List<Result>
// ingredients with common ingredients are grouped into lists (ex. dirt) // ingredients with common ingredients are grouped into lists (ex. dirt)
for (var ingredient : ingredientGrouper.keySet()) { for (var ingredient : ingredientGrouper.keySet()) {
Multimap<Item, SieveRecipe> meshGrouper = ArrayListMultimap.create(); Multimap<Item, RecipeHolder<T>> meshGrouper = ArrayListMultimap.create();
var values = ingredientGrouper.get(ingredient); var values = ingredientGrouper.get(ingredient);
// A unique ingredient ID, which can grow long. For same ingredient will always generate same ID, even over game restarts.
var ingredientId = "ingredient-start " + Stream
.of(ingredient.getItems())
.map(ItemStack::getItem)
.map(BuiltInRegistries.ITEM::getKey)
.map(ResourceLocation::toString)
.sorted()
.collect(Collectors.joining(" - ")) + " ingredient-end ";
// these lists are grouped into sub lists based on their meshes (ex. dirt with string mesh) // these lists are grouped into sub lists based on their meshes (ex. dirt with string mesh)
for (var recipe : values) { for (var holder : values) {
var recipe = holder.value();
for (var stack : recipe.mesh.getItems()) { for (var stack : recipe.mesh.getItems()) {
meshGrouper.put(stack.getItem(), recipe); meshGrouper.put(stack.getItem(), holder);
} }
} }
@ -91,15 +115,22 @@ public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List<Result>
for (var mesh : meshes) { for (var mesh : meshes) {
var meshRecipes = meshGrouper.get(mesh); var meshRecipes = meshGrouper.get(mesh);
var results = new ArrayList<Result>(meshRecipes.size()); var results = new ArrayList<Result>(meshRecipes.size());
var idList = new ArrayList<String>();
idList.add(ingredientId);
idList.add(BuiltInRegistries.ITEM.getKey(mesh).toString());
for (var recipe : meshRecipes) { for (var holder : meshRecipes) {
int resultCount = recipe.resultAmount instanceof ConstantValue constant ? Math.round(constant.value()) : 1; var recipe = holder.value();
results.add(new Result(recipe.result.copyWithCount(resultCount), recipe.resultAmount, recipe.byHandOnly)); int resultCount = recipe.resultAmount instanceof ConstantValue(float value) ? Math.round(value) : 1;
results.add(new Result(holder, recipe.result.copyWithCount(resultCount), recipe.resultAmount, recipe.byHandOnly));
idList.add(holder.id().toString());
} }
results.sort(resultSorter); var id = ResourceLocation.fromNamespaceAndPath(recipeTypeKey.getNamespace(), recipeTypeKey.getPath() + "/" + hash512(idList));
var jeiRecipe = new XeiSieveRecipe(ingredient, new ItemStack(mesh), results); results.sort(resultSorter);
var jeiRecipe = new XeiSieveRecipe(id, ingredient, new ItemStack(mesh), results);
jeiRecipes.add(jeiRecipe); jeiRecipes.add(jeiRecipe);
var rows = Mth.ceil((float) meshRecipes.size() / 9f); var rows = Mth.ceil((float) meshRecipes.size() / 9f);
@ -132,13 +163,39 @@ public record XeiSieveRecipe(Ingredient ingredient, ItemStack mesh, List<Result>
} }
} }
/**
* Function to hash a collection of strings using 512 bits precision.
* The order of the input collection does not matter.
* The idea behind this is to hash all recipes that make a recipe group and give the recipe group an ID.
* We need to make sure the ID doesn't end up with any collisions, so we just use a cryptographic hash.
* It's much more likely that 100 meteors strike ones house at the same time, than that two hashes collide,
* so that should suffice for uniqueness...
*/
private static String hash512(Collection<String> inputs) {
try {
// make a unique string out of all inputs, regardless of the order they have.
// we use a separator which is sure to be unique and never in any input: " ||| "
var sortedInputs = inputs.stream().sorted().collect(Collectors.joining(" ||| "));
var md = MessageDigest.getInstance("SHA-512");
var digest = md.digest(sortedInputs.getBytes(StandardCharsets.UTF_8));
// Create a bigint out of the digest and convert it to a hex string
var bi = new BigInteger(1, digest);
return bi.toString(16);
} catch (NoSuchAlgorithmException e) {
// This is pretty bad... This shouldn't happen
throw new Error("Your java does not support SHA-512. Wat da hell? Report this to ExDeorum", e);
}
}
public static final class Result { public static final class Result {
public final RecipeHolder<? extends SieveRecipe> holder;
public final ItemStack item; public final ItemStack item;
public final NumberProvider provider; public final NumberProvider provider;
public final boolean byHandOnly; public final boolean byHandOnly;
private final double expectedCount; private final double expectedCount;
Result(ItemStack item, NumberProvider provider, boolean byHandOnly) { Result(RecipeHolder<? extends SieveRecipe> holder, ItemStack item, NumberProvider provider, boolean byHandOnly) {
this.holder = holder;
this.item = item; this.item = item;
this.provider = provider; this.provider = provider;
this.byHandOnly = byHandOnly; this.byHandOnly = byHandOnly;

View File

@ -29,11 +29,12 @@ import mezz.jei.api.recipe.category.IRecipeCategory;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.crafting.RecipeHolder;
import thedarkcolour.exdeorum.compat.ClientXeiUtil; import thedarkcolour.exdeorum.compat.ClientXeiUtil;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.recipe.barrel.BarrelCompostRecipe; import thedarkcolour.exdeorum.recipe.barrel.BarrelCompostRecipe;
class BarrelCompostCategory implements IRecipeCategory<BarrelCompostRecipe> { class BarrelCompostCategory implements IRecipeCategory<RecipeHolder<BarrelCompostRecipe>> {
public static final int WIDTH = 120; public static final int WIDTH = 120;
public static final int HEIGHT = 18; public static final int HEIGHT = 18;
@ -50,7 +51,7 @@ class BarrelCompostCategory implements IRecipeCategory<BarrelCompostRecipe> {
} }
@Override @Override
public RecipeType<BarrelCompostRecipe> getRecipeType() { public RecipeType<RecipeHolder<BarrelCompostRecipe>> getRecipeType() {
return ExDeorumJeiPlugin.BARREL_COMPOST; return ExDeorumJeiPlugin.BARREL_COMPOST;
} }
@ -70,15 +71,15 @@ class BarrelCompostCategory implements IRecipeCategory<BarrelCompostRecipe> {
} }
@Override @Override
public void setRecipe(IRecipeLayoutBuilder builder, BarrelCompostRecipe recipe, IFocusGroup focuses) { public void setRecipe(IRecipeLayoutBuilder builder, RecipeHolder<BarrelCompostRecipe> holder, IFocusGroup focuses) {
builder.addSlot(RecipeIngredientRole.INPUT, 1, 1).addIngredients(recipe.ingredient()); builder.addSlot(RecipeIngredientRole.INPUT, 1, 1).addIngredients(holder.value().ingredient());
} }
@Override @Override
public void draw(BarrelCompostRecipe recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { public void draw(RecipeHolder<BarrelCompostRecipe> holder, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) {
this.slot.draw(graphics); this.slot.draw(graphics);
var volume = recipe.getVolume(); var volume = holder.value().getVolume();
var volumeLabel = Component.translatable(TranslationKeys.BARREL_COMPOST_RECIPE_VOLUME, volume); var volumeLabel = Component.translatable(TranslationKeys.BARREL_COMPOST_RECIPE_VOLUME, volume);
graphics.drawString(Minecraft.getInstance().font, volumeLabel, 24, 5, 0xff808080, false); graphics.drawString(Minecraft.getInstance().font, volumeLabel, 24, 5, 0xff808080, false);

View File

@ -31,6 +31,7 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import thedarkcolour.exdeorum.compat.ClientXeiUtil; import thedarkcolour.exdeorum.compat.ClientXeiUtil;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.material.DefaultMaterials; import thedarkcolour.exdeorum.material.DefaultMaterials;
@ -81,25 +82,26 @@ public abstract class BarrelMixingCategory<T> implements IRecipeCategory<T> {
this.slot.draw(graphics, 78, 0); this.slot.draw(graphics, 78, 0);
} }
public static class Items extends BarrelMixingCategory<BarrelMixingRecipe> { public static class Items extends BarrelMixingCategory<RecipeHolder<BarrelMixingRecipe>> {
public Items(IGuiHelper helper, IDrawable plus, IDrawable arrow) { public Items(IGuiHelper helper, IDrawable plus, IDrawable arrow) {
super(helper, plus, arrow, TranslationKeys.BARREL_MIXING_CATEGORY_TITLE, DefaultMaterials.OAK_BARREL.getItem()); super(helper, plus, arrow, TranslationKeys.BARREL_MIXING_CATEGORY_TITLE, DefaultMaterials.OAK_BARREL.getItem());
} }
@Override @Override
public void setRecipe(IRecipeLayoutBuilder builder, BarrelMixingRecipe recipe, IFocusGroup focuses) { public void setRecipe(IRecipeLayoutBuilder builder, RecipeHolder<BarrelMixingRecipe> holder, IFocusGroup focuses) {
var recipe = holder.value();
JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 1, 1), recipe.fluid).setFluidRenderer(1000, false, 16, 16); JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 1, 1), recipe.fluid).setFluidRenderer(1000, false, 16, 16);
builder.addSlot(RecipeIngredientRole.INPUT, 33, 1).addIngredients(recipe.ingredient()); builder.addSlot(RecipeIngredientRole.INPUT, 33, 1).addIngredients(recipe.ingredient());
builder.addSlot(RecipeIngredientRole.OUTPUT, 79, 1).addItemStack(recipe.result); builder.addSlot(RecipeIngredientRole.OUTPUT, 79, 1).addItemStack(recipe.result);
} }
@Override @Override
public RecipeType<BarrelMixingRecipe> getRecipeType() { public RecipeType<RecipeHolder<BarrelMixingRecipe>> getRecipeType() {
return ExDeorumJeiPlugin.BARREL_MIXING; return ExDeorumJeiPlugin.BARREL_MIXING;
} }
} }
public static class Fluids extends BarrelMixingCategory<BarrelFluidMixingRecipe> { public static class Fluids extends BarrelMixingCategory<RecipeHolder<BarrelFluidMixingRecipe>> {
private static final Component CONTENTS_ARE_CONSUMED_TOOLTIP = Component.translatable(TranslationKeys.BARREL_FLUID_MIXING_CONTENTS_ARE_CONSUMED).withStyle(ChatFormatting.RED); private static final Component CONTENTS_ARE_CONSUMED_TOOLTIP = Component.translatable(TranslationKeys.BARREL_FLUID_MIXING_CONTENTS_ARE_CONSUMED).withStyle(ChatFormatting.RED);
public Fluids(IGuiHelper helper, IDrawable plus, IDrawable arrow) { public Fluids(IGuiHelper helper, IDrawable plus, IDrawable arrow) {
@ -107,7 +109,8 @@ public abstract class BarrelMixingCategory<T> implements IRecipeCategory<T> {
} }
@Override @Override
public void setRecipe(IRecipeLayoutBuilder builder, BarrelFluidMixingRecipe recipe, IFocusGroup focuses) { public void setRecipe(IRecipeLayoutBuilder builder, RecipeHolder<BarrelFluidMixingRecipe> holder, IFocusGroup focuses) {
var recipe = holder.value();
JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 1, 1), recipe.baseFluid()) JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 1, 1), recipe.baseFluid())
.setFluidRenderer(1000, false, 16, 16); .setFluidRenderer(1000, false, 16, 16);
var additiveSlot = JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 33, 1), recipe.additiveFluid(), 1000) var additiveSlot = JeiUtil.addFluidIngredient(builder.addSlot(RecipeIngredientRole.INPUT, 33, 1), recipe.additiveFluid(), 1000)
@ -119,15 +122,15 @@ public abstract class BarrelMixingCategory<T> implements IRecipeCategory<T> {
} }
@Override @Override
public RecipeType<BarrelFluidMixingRecipe> getRecipeType() { public RecipeType<RecipeHolder<BarrelFluidMixingRecipe>> getRecipeType() {
return ExDeorumJeiPlugin.BARREL_FLUID_MIXING; return ExDeorumJeiPlugin.BARREL_FLUID_MIXING;
} }
@Override @Override
public void draw(BarrelFluidMixingRecipe recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { public void draw(RecipeHolder<BarrelFluidMixingRecipe> holder, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) {
super.draw(recipe, recipeSlotsView, graphics, mouseX, mouseY); super.draw(holder, recipeSlotsView, graphics, mouseX, mouseY);
if (recipe.consumesAdditive()) { if (holder.value().consumesAdditive()) {
ClientXeiUtil.renderAsterisk(graphics, 18 + 3 + 3 + 8, 0); ClientXeiUtil.renderAsterisk(graphics, 18 + 3 + 3 + 8, 0);
} }
} }

View File

@ -1,6 +1,6 @@
package thedarkcolour.exdeorum.compat.jei; package thedarkcolour.exdeorum.compat.jei;
import mezz.jei.api.helpers.IGuiHelper; import mezz.jei.api.helpers.IJeiHelpers;
import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.RecipeType;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import thedarkcolour.exdeorum.compat.XeiSieveRecipe; import thedarkcolour.exdeorum.compat.XeiSieveRecipe;
@ -8,8 +8,8 @@ import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.material.DefaultMaterials; import thedarkcolour.exdeorum.material.DefaultMaterials;
class CompressedSieveCategory extends SieveCategory { class CompressedSieveCategory extends SieveCategory {
CompressedSieveCategory(IGuiHelper helper) { CompressedSieveCategory(IJeiHelpers jeiHelpers) {
super(helper, DefaultMaterials.OAK_COMPRESSED_SIEVE, Component.translatable(TranslationKeys.COMPRESSED_SIEVE_CATEGORY_TITLE), XeiSieveRecipe.COMPRESSED_SIEVE_ROWS); super(jeiHelpers, DefaultMaterials.OAK_COMPRESSED_SIEVE, Component.translatable(TranslationKeys.COMPRESSED_SIEVE_CATEGORY_TITLE), XeiSieveRecipe.COMPRESSED_SIEVE_ROWS);
} }
@Override @Override

View File

@ -36,8 +36,10 @@ import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.compat.ClientXeiUtil; import thedarkcolour.exdeorum.compat.ClientXeiUtil;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.registry.EItems; import thedarkcolour.exdeorum.registry.EItems;
@ -98,6 +100,11 @@ public class CrookCategory implements IRecipeCategory<CrookJeiRecipe> {
}); });
} }
@Override
public @Nullable ResourceLocation getRegistryName(CrookJeiRecipe recipe) {
return recipe.identifier;
}
@Override @Override
public void draw(CrookJeiRecipe recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) { public void draw(CrookJeiRecipe recipe, IRecipeSlotsView recipeSlotsView, GuiGraphics graphics, double mouseX, double mouseY) {
this.timer.onDraw(); this.timer.onDraw();

View File

@ -23,9 +23,11 @@ import mezz.jei.api.gui.builder.IRecipeLayoutBuilder;
import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeIngredientRole;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BlockStateProperties;
@ -39,11 +41,13 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
public sealed abstract class CrookJeiRecipe { public sealed abstract class CrookJeiRecipe {
public final ResourceLocation identifier;
public final List<BlockState> states; public final List<BlockState> states;
public ItemStack result; public ItemStack result;
public float chance; public float chance;
public CrookJeiRecipe(List<BlockState> states, ItemStack result, float chance) { public CrookJeiRecipe(ResourceLocation identifier, List<BlockState> states, ItemStack result, float chance) {
this.identifier = identifier;
this.states = states; this.states = states;
this.result = result; this.result = result;
this.chance = chance; this.chance = chance;
@ -51,13 +55,18 @@ public sealed abstract class CrookJeiRecipe {
public abstract void addIngredients(IRecipeLayoutBuilder builder); public abstract void addIngredients(IRecipeLayoutBuilder builder);
static CrookJeiRecipe create(CrookRecipe recipe) { static CrookJeiRecipe create(RecipeHolder<CrookRecipe> recipeHolder) {
var recipe = recipeHolder.value();
var id = recipeHolder.id();
switch (recipe.blockPredicate()) { switch (recipe.blockPredicate()) {
case BlockPredicate.BlockStatePredicate state -> { case BlockPredicate.BlockStatePredicate state -> {
return new StatesRecipe(state, state.possibleStates().filter(blockState -> !blockState.hasProperty(BlockStateProperties.WATERLOGGED) || !blockState.getValue(BlockStateProperties.WATERLOGGED)).toList(), recipe.result(), recipe.chance()); return new StatesRecipe(id, state, state
.possibleStates()
.filter(blockState -> !blockState.hasProperty(BlockStateProperties.WATERLOGGED) || !blockState.getValue(BlockStateProperties.WATERLOGGED))
.toList(), recipe.result(), recipe.chance());
} }
case BlockPredicate.SingleBlockPredicate block -> { case BlockPredicate.SingleBlockPredicate block -> {
return new BlockRecipe(block.block(), recipe.result(), recipe.chance()); return new BlockRecipe(id, block.block(), recipe.result(), recipe.chance());
} }
case BlockPredicate.TagPredicate tag -> { case BlockPredicate.TagPredicate tag -> {
var list = new ArrayList<BlockState>(); var list = new ArrayList<BlockState>();
@ -68,7 +77,7 @@ public sealed abstract class CrookJeiRecipe {
} }
} }
return new TagRecipe(tag.tag(), List.copyOf(list), recipe.result(), recipe.chance()); return new TagRecipe(id, tag.tag(), List.copyOf(list), recipe.result(), recipe.chance());
} }
default -> throw new IllegalArgumentException("Invalid crook recipe?? -> " + recipe); default -> throw new IllegalArgumentException("Invalid crook recipe?? -> " + recipe);
} }
@ -78,8 +87,8 @@ public sealed abstract class CrookJeiRecipe {
private final List<ItemStack> itemIngredients; private final List<ItemStack> itemIngredients;
public final List<Component> requirements; public final List<Component> requirements;
StatesRecipe(@Nullable BlockPredicate.BlockStatePredicate predicate, List<BlockState> states, ItemStack result, float chance) { StatesRecipe(ResourceLocation identifier, @Nullable BlockPredicate.BlockStatePredicate predicate, List<BlockState> states, ItemStack result, float chance) {
super(states, result, chance); super(identifier, states, result, chance);
ImmutableList.Builder<ItemStack> itemIngredients = ImmutableList.builder(); ImmutableList.Builder<ItemStack> itemIngredients = ImmutableList.builder();
var blocks = new HashSet<Block>(); var blocks = new HashSet<Block>();
@ -112,8 +121,8 @@ public sealed abstract class CrookJeiRecipe {
static final class TagRecipe extends StatesRecipe { static final class TagRecipe extends StatesRecipe {
public final TagKey<Block> tag; public final TagKey<Block> tag;
public TagRecipe(TagKey<Block> tag, List<BlockState> states, ItemStack result, float chance) { public TagRecipe(ResourceLocation identifier, TagKey<Block> tag, List<BlockState> states, ItemStack result, float chance) {
super(null, states, result, chance); super(identifier, null, states, result, chance);
this.tag = tag; this.tag = tag;
} }
} }
@ -121,8 +130,8 @@ public sealed abstract class CrookJeiRecipe {
static final class BlockRecipe extends CrookJeiRecipe { static final class BlockRecipe extends CrookJeiRecipe {
private final ItemStack itemIngredient; private final ItemStack itemIngredient;
BlockRecipe(Block block, ItemStack result, float chance) { BlockRecipe(ResourceLocation identifier, Block block, ItemStack result, float chance) {
super(ImmutableList.of(block.defaultBlockState()), result, chance); super(identifier, ImmutableList.of(block.defaultBlockState()), result, chance);
var item = block.asItem(); var item = block.asItem();
if (item == Items.AIR) { if (item == Items.AIR) {

View File

@ -25,22 +25,25 @@ import mezz.jei.api.recipe.RecipeType;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.material.DefaultMaterials; import thedarkcolour.exdeorum.material.DefaultMaterials;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe; import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
abstract class CrucibleCategory<T extends CrucibleRecipe> extends OneToOneCategory<T> { abstract class CrucibleCategory<T extends CrucibleRecipe> extends OneToOneCategory<RecipeHolder<T>> {
public CrucibleCategory(IGuiHelper helper, IDrawable arrow, Item iconItem, String titleKey) { public CrucibleCategory(IGuiHelper helper, IDrawable arrow, Item iconItem, String titleKey) {
super(helper, arrow, helper.createDrawableItemStack(new ItemStack(iconItem)), Component.translatable(titleKey)); super(helper, arrow, helper.createDrawableItemStack(new ItemStack(iconItem)), Component.translatable(titleKey));
} }
@Override @Override
protected void addInput(IRecipeSlotBuilder slot, CrucibleRecipe recipe) { protected void addInput(IRecipeSlotBuilder slot, RecipeHolder<T> holder) {
var recipe = holder.value();
slot.addIngredients(recipe.ingredient()); slot.addIngredients(recipe.ingredient());
} }
@Override @Override
protected void addOutput(IRecipeSlotBuilder slot, CrucibleRecipe recipe) { protected void addOutput(IRecipeSlotBuilder slot, RecipeHolder<T> holder) {
var recipe = holder.value();
slot.addFluidStack(recipe.getResult().getFluid(), recipe.getResult().getAmount()) slot.addFluidStack(recipe.getResult().getFluid(), recipe.getResult().getAmount())
.setFluidRenderer(Math.max(1000, recipe.getResult().getAmount()), false, 16, 16); .setFluidRenderer(Math.max(1000, recipe.getResult().getAmount()), false, 16, 16);
} }
@ -51,7 +54,7 @@ abstract class CrucibleCategory<T extends CrucibleRecipe> extends OneToOneCatego
} }
@Override @Override
public RecipeType<CrucibleRecipe.Lava> getRecipeType() { public RecipeType<RecipeHolder<CrucibleRecipe.Lava>> getRecipeType() {
return ExDeorumJeiPlugin.LAVA_CRUCIBLE; return ExDeorumJeiPlugin.LAVA_CRUCIBLE;
} }
} }
@ -62,7 +65,7 @@ abstract class CrucibleCategory<T extends CrucibleRecipe> extends OneToOneCatego
} }
@Override @Override
public RecipeType<CrucibleRecipe.Water> getRecipeType() { public RecipeType<RecipeHolder<CrucibleRecipe.Water>> getRecipeType() {
return ExDeorumJeiPlugin.WATER_CRUCIBLE; return ExDeorumJeiPlugin.WATER_CRUCIBLE;
} }
} }

View File

@ -19,11 +19,13 @@
package thedarkcolour.exdeorum.compat.jei; package thedarkcolour.exdeorum.compat.jei;
import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.IIngredientType;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleHeatRecipe;
final class CrucibleHeatSourceRecipe { final class CrucibleHeatSourceRecipe {
private final int meltRate; private final RecipeHolder<CrucibleHeatRecipe> recipeHolder;
private final BlockState blockState; private final BlockState blockState;
@Nullable @Nullable
private final IIngredientType<Object> ingredientType; private final IIngredientType<Object> ingredientType;
@ -31,15 +33,19 @@ final class CrucibleHeatSourceRecipe {
private final Object ingredient; private final Object ingredient;
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
CrucibleHeatSourceRecipe(int meltRate, BlockState blockState, @Nullable IIngredientType ingredientType, @Nullable Object ingredient) { CrucibleHeatSourceRecipe(RecipeHolder<CrucibleHeatRecipe> recipeHolder, BlockState blockState, @Nullable IIngredientType ingredientType, @Nullable Object ingredient) {
this.meltRate = meltRate; this.recipeHolder = recipeHolder;
this.blockState = blockState; this.blockState = blockState;
this.ingredientType = ingredientType; this.ingredientType = ingredientType;
this.ingredient = ingredient; this.ingredient = ingredient;
} }
public RecipeHolder<CrucibleHeatRecipe> recipeHolder() {
return recipeHolder;
}
public int meltRate() { public int meltRate() {
return this.meltRate; return this.recipeHolder.value().heatValue();
} }
public BlockState blockState() { public BlockState blockState() {

View File

@ -35,8 +35,10 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.TooltipFlag;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.compat.ClientXeiUtil; import thedarkcolour.exdeorum.compat.ClientXeiUtil;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.material.DefaultMaterials; import thedarkcolour.exdeorum.material.DefaultMaterials;
@ -106,6 +108,11 @@ class CrucibleHeatSourcesCategory implements IRecipeCategory<CrucibleHeatSourceR
ClientXeiUtil.renderBlock(graphics, recipe.blockState(), 60, 24, 10, 20F); ClientXeiUtil.renderBlock(graphics, recipe.blockState(), 60, 24, 10, 20F);
} }
@Override
public @Nullable ResourceLocation getRegistryName(CrucibleHeatSourceRecipe recipe) {
return recipe.recipeHolder().id();
}
@Override @Override
public List<Component> getTooltipStrings(CrucibleHeatSourceRecipe recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) { public List<Component> getTooltipStrings(CrucibleHeatSourceRecipe recipe, IRecipeSlotsView recipeSlotsView, double mouseX, double mouseY) {
if (44.0 < mouseX && mouseX < 76.0 && 16 < mouseY && mouseY < 48) { if (44.0 < mouseX && mouseX < 76.0 && 16 < mouseY && mouseY < 48) {

View File

@ -18,7 +18,6 @@
package thedarkcolour.exdeorum.compat.jei; package thedarkcolour.exdeorum.compat.jei;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import mezz.jei.api.IModPlugin; import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin; import mezz.jei.api.JeiPlugin;
import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.constants.VanillaTypes;
@ -37,6 +36,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeInput; import net.minecraft.world.item.crafting.RecipeInput;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
@ -44,7 +44,6 @@ import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.WallTorchBlock; import net.minecraft.world.level.block.WallTorchBlock;
import net.neoforged.fml.ModList; import net.neoforged.fml.ModList;
import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.registries.DeferredHolder;
import thedarkcolour.exdeorum.ExDeorum; import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.client.ClientsideCode; import thedarkcolour.exdeorum.client.ClientsideCode;
import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen; import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen;
@ -58,6 +57,7 @@ import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.barrel.BarrelCompostRecipe; import thedarkcolour.exdeorum.recipe.barrel.BarrelCompostRecipe;
import thedarkcolour.exdeorum.recipe.barrel.BarrelFluidMixingRecipe; import thedarkcolour.exdeorum.recipe.barrel.BarrelFluidMixingRecipe;
import thedarkcolour.exdeorum.recipe.barrel.BarrelMixingRecipe; import thedarkcolour.exdeorum.recipe.barrel.BarrelMixingRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleHeatRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe; import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
import thedarkcolour.exdeorum.recipe.hammer.CompressedHammerRecipe; import thedarkcolour.exdeorum.recipe.hammer.CompressedHammerRecipe;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe; import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
@ -68,6 +68,7 @@ import thedarkcolour.exdeorum.tag.EItemTags;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -76,18 +77,23 @@ import java.util.function.Supplier;
public class ExDeorumJeiPlugin implements IModPlugin { public class ExDeorumJeiPlugin implements IModPlugin {
public static final ResourceLocation EX_DEORUM_JEI_TEXTURE = ExDeorum.loc("textures/gui/jei/enr_jei.png"); public static final ResourceLocation EX_DEORUM_JEI_TEXTURE = ExDeorum.loc("textures/gui/jei/enr_jei.png");
static final RecipeType<BarrelCompostRecipe> BARREL_COMPOST = recipeType("barrel_compost", BarrelCompostRecipe.class); static final RecipeType<RecipeHolder<BarrelCompostRecipe>> BARREL_COMPOST = recipeType("barrel_compost");
static final RecipeType<BarrelMixingRecipe> BARREL_MIXING = recipeType("barrel_mixing", BarrelMixingRecipe.class); static final RecipeType<RecipeHolder<BarrelMixingRecipe>> BARREL_MIXING = recipeType("barrel_mixing");
static final RecipeType<BarrelFluidMixingRecipe> BARREL_FLUID_MIXING = recipeType("barrel_fluid_mixing", BarrelFluidMixingRecipe.class); static final RecipeType<RecipeHolder<BarrelFluidMixingRecipe>> BARREL_FLUID_MIXING = recipeType("barrel_fluid_mixing");
static final RecipeType<CrucibleRecipe.Lava> LAVA_CRUCIBLE = recipeType("lava_crucible", CrucibleRecipe.Lava.class); static final RecipeType<RecipeHolder<CrucibleRecipe.Lava>> LAVA_CRUCIBLE = recipeType("lava_crucible");
static final RecipeType<CrucibleRecipe.Water> WATER_CRUCIBLE = recipeType("water_crucible", CrucibleRecipe.Water.class); static final RecipeType<RecipeHolder<CrucibleRecipe.Water>> WATER_CRUCIBLE = recipeType("water_crucible");
static final RecipeType<CrucibleHeatSourceRecipe> CRUCIBLE_HEAT_SOURCES = recipeType("crucible_heat_sources", CrucibleHeatSourceRecipe.class); static final RecipeType<CrucibleHeatSourceRecipe> CRUCIBLE_HEAT_SOURCES = recipeType("crucible_heat_sources", CrucibleHeatSourceRecipe.class);
static final RecipeType<XeiSieveRecipe> SIEVE = recipeType("sieve", XeiSieveRecipe.class); static final RecipeType<XeiSieveRecipe> SIEVE = recipeType("sieve", XeiSieveRecipe.class);
static final RecipeType<XeiSieveRecipe> COMPRESSED_SIEVE = recipeType("compressed_sieve", XeiSieveRecipe.class); static final RecipeType<XeiSieveRecipe> COMPRESSED_SIEVE = recipeType("compressed_sieve", XeiSieveRecipe.class);
static final RecipeType<HammerRecipe> HAMMER = recipeType("hammer", HammerRecipe.class); static final RecipeType<RecipeHolder<HammerRecipe>> HAMMER = recipeType("hammer");
static final RecipeType<HammerRecipe> COMPRESSED_HAMMER = recipeType("compressed_hammer", CompressedHammerRecipe.class); static final RecipeType<RecipeHolder<CompressedHammerRecipe>> COMPRESSED_HAMMER = recipeType("compressed_hammer");
static final RecipeType<CrookJeiRecipe> CROOK = recipeType("crook", CrookJeiRecipe.class); static final RecipeType<CrookJeiRecipe> CROOK = recipeType("crook", CrookJeiRecipe.class);
private static <T extends Recipe<?>> RecipeType<RecipeHolder<T>> recipeType(String path) {
String namespace = ModList.get().isLoaded(ModIds.EMI) ? ExDeorum.ID + "_" + ModIds.EMI : ExDeorum.ID;
return RecipeType.createRecipeHolderType(ResourceLocation.fromNamespaceAndPath(namespace, path));
}
private static <T> RecipeType<T> recipeType(String path, Class<? extends T> type) { private static <T> RecipeType<T> recipeType(String path, Class<? extends T> type) {
// use alternative namespace so that EMI doesn't skip JEI compatibility // use alternative namespace so that EMI doesn't skip JEI compatibility
String namespace = ModList.get().isLoaded(ModIds.EMI) ? ExDeorum.ID + "_" + ModIds.EMI : ExDeorum.ID; String namespace = ModList.get().isLoaded(ModIds.EMI) ? ExDeorum.ID + "_" + ModIds.EMI : ExDeorum.ID;
@ -111,10 +117,10 @@ public class ExDeorumJeiPlugin implements IModPlugin {
registration.addRecipeCategories(new CrucibleCategory.LavaCrucible(helper, arrow)); registration.addRecipeCategories(new CrucibleCategory.LavaCrucible(helper, arrow));
registration.addRecipeCategories(new CrucibleCategory.WaterCrucible(helper, arrow)); registration.addRecipeCategories(new CrucibleCategory.WaterCrucible(helper, arrow));
registration.addRecipeCategories(new CrucibleHeatSourcesCategory(registration.getJeiHelpers())); registration.addRecipeCategories(new CrucibleHeatSourcesCategory(registration.getJeiHelpers()));
registration.addRecipeCategories(new SieveCategory(helper)); registration.addRecipeCategories(new SieveCategory(registration.getJeiHelpers()));
registration.addRecipeCategories(new CompressedSieveCategory(helper)); registration.addRecipeCategories(new CompressedSieveCategory(registration.getJeiHelpers()));
registration.addRecipeCategories(new HammerCategory(helper, arrow, EItems.DIAMOND_HAMMER, Component.translatable(TranslationKeys.HAMMER_CATEGORY_TITLE), HAMMER)); registration.addRecipeCategories(new HammerCategory<>(helper, arrow, EItems.DIAMOND_HAMMER, Component.translatable(TranslationKeys.HAMMER_CATEGORY_TITLE), HAMMER));
registration.addRecipeCategories(new HammerCategory(helper, arrow, EItems.COMPRESSED_DIAMOND_HAMMER, Component.translatable(TranslationKeys.COMPRESSED_HAMMER_CATEGORY_TITLE), COMPRESSED_HAMMER)); registration.addRecipeCategories(new HammerCategory<>(helper, arrow, EItems.COMPRESSED_DIAMOND_HAMMER, Component.translatable(TranslationKeys.COMPRESSED_HAMMER_CATEGORY_TITLE), COMPRESSED_HAMMER));
registration.addRecipeCategories(new CrookCategory(registration.getJeiHelpers(), arrow)); registration.addRecipeCategories(new CrookCategory(registration.getJeiHelpers(), arrow));
} }
@ -214,8 +220,7 @@ public class ExDeorumJeiPlugin implements IModPlugin {
addRecipes(registration, LAVA_CRUCIBLE, ERecipeTypes.LAVA_CRUCIBLE); addRecipes(registration, LAVA_CRUCIBLE, ERecipeTypes.LAVA_CRUCIBLE);
addRecipes(registration, WATER_CRUCIBLE, ERecipeTypes.WATER_CRUCIBLE); addRecipes(registration, WATER_CRUCIBLE, ERecipeTypes.WATER_CRUCIBLE);
addRecipes(registration, HAMMER, ERecipeTypes.HAMMER); addRecipes(registration, HAMMER, ERecipeTypes.HAMMER);
//noinspection rawtypes,unchecked addRecipes(registration, COMPRESSED_HAMMER, ERecipeTypes.COMPRESSED_HAMMER);
addRecipes(registration, COMPRESSED_HAMMER, ((DeferredHolder) ERecipeTypes.COMPRESSED_HAMMER));
registration.addRecipes(CROOK, CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), ERecipeTypes.CROOK.get(), CrookJeiRecipe::create)); registration.addRecipes(CROOK, CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), ERecipeTypes.CROOK.get(), CrookJeiRecipe::create));
registration.addRecipes(SIEVE, XeiSieveRecipe.getAllRecipesGrouped(ERecipeTypes.SIEVE.get(), XeiSieveRecipe.SIEVE_ROWS)); registration.addRecipes(SIEVE, XeiSieveRecipe.getAllRecipesGrouped(ERecipeTypes.SIEVE.get(), XeiSieveRecipe.SIEVE_ROWS));
registration.addRecipes(COMPRESSED_SIEVE, XeiSieveRecipe.getAllRecipesGrouped(ERecipeTypes.COMPRESSED_SIEVE.get(), XeiSieveRecipe.COMPRESSED_SIEVE_ROWS)); registration.addRecipes(COMPRESSED_SIEVE, XeiSieveRecipe.getAllRecipesGrouped(ERecipeTypes.COMPRESSED_SIEVE.get(), XeiSieveRecipe.COMPRESSED_SIEVE_ROWS));
@ -224,7 +229,7 @@ public class ExDeorumJeiPlugin implements IModPlugin {
} }
private static void addCrucibleHeatSources(IRecipeRegistration registration) { private static void addCrucibleHeatSources(IRecipeRegistration registration) {
var values = new Object2IntOpenHashMap<Block>(); var values = new HashMap<Block, RecipeHolder<CrucibleHeatRecipe>>();
for (var entry : ClientsideCode.getRecipeCaches().getHeatSources()) { for (var entry : ClientsideCode.getRecipeCaches().getHeatSources()) {
var state = entry.getKey(); var state = entry.getKey();
var block = state.getBlock(); var block = state.getBlock();
@ -233,13 +238,18 @@ public class ExDeorumJeiPlugin implements IModPlugin {
if (block != Blocks.AIR) { if (block != Blocks.AIR) {
final int newValue = entry.getValue().value().heatValue(); final int newValue = entry.getValue().value().heatValue();
if (newValue == 0) {
// we don't want to display useless heat sources
// why would people even create these?
continue;
}
values.computeInt(block, (key, value) -> { values.compute(block, (key, value) -> {
if (value != null) { if (value != null && value.value().heatValue() > newValue) {
return Math.max(value, newValue); // Existing entry is a better heat source
} else { return value;
return newValue == 0 ? null : newValue;
} }
return entry.getValue();
}); });
} }
} }
@ -247,16 +257,17 @@ public class ExDeorumJeiPlugin implements IModPlugin {
var fluidIngredientType = fluidHelper.getFluidIngredientType(); var fluidIngredientType = fluidHelper.getFluidIngredientType();
var recipes = new ArrayList<CrucibleHeatSourceRecipe>(); var recipes = new ArrayList<CrucibleHeatSourceRecipe>();
for (var entry : values.object2IntEntrySet()) { for (var entry : values.entrySet()) {
var holder = entry.getValue();
if (entry.getKey() instanceof LiquidBlock liquid) { if (entry.getKey() instanceof LiquidBlock liquid) {
recipes.add(new CrucibleHeatSourceRecipe(entry.getIntValue(), entry.getKey().defaultBlockState(), fluidIngredientType, fluidHelper.create(Holder.direct(liquid.fluid), 1000))); recipes.add(new CrucibleHeatSourceRecipe(holder, entry.getKey().defaultBlockState(), fluidIngredientType, fluidHelper.create(Holder.direct(liquid.fluid), 1000)));
} else { } else {
var itemForm = entry.getKey().asItem(); var itemForm = entry.getKey().asItem();
if (itemForm != Items.AIR) { if (itemForm != Items.AIR) {
recipes.add(new CrucibleHeatSourceRecipe(entry.getIntValue(), entry.getKey().defaultBlockState(), VanillaTypes.ITEM_STACK, new ItemStack(itemForm))); recipes.add(new CrucibleHeatSourceRecipe(holder, entry.getKey().defaultBlockState(), VanillaTypes.ITEM_STACK, new ItemStack(itemForm)));
} else { } else {
recipes.add(new CrucibleHeatSourceRecipe(entry.getIntValue(), entry.getKey().defaultBlockState(), null, null)); recipes.add(new CrucibleHeatSourceRecipe(holder, entry.getKey().defaultBlockState(), null, null));
} }
} }
} }
@ -300,7 +311,7 @@ public class ExDeorumJeiPlugin implements IModPlugin {
}); });
} }
private static <C extends RecipeInput, T extends Recipe<C>> void addRecipes(IRecipeRegistration registration, RecipeType<T> category, Supplier<net.minecraft.world.item.crafting.RecipeType<T>> type) { private static <C extends RecipeInput, T extends Recipe<C>> void addRecipes(IRecipeRegistration registration, RecipeType<RecipeHolder<T>> category, Supplier<net.minecraft.world.item.crafting.RecipeType<T>> type) {
registration.addRecipes(category, CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), type.get(), Function.identity())); registration.addRecipes(category, CompatUtil.collectAllRecipes(RecipeUtil.getClientRecipeManager(), type.get(), Function.identity()));
} }
} }

View File

@ -23,34 +23,38 @@ import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.helpers.IGuiHelper; import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.RecipeType;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe; import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
import java.util.function.Supplier; import java.util.function.Supplier;
class HammerCategory extends OneToOneCategory<HammerRecipe> { class HammerCategory<R extends HammerRecipe> extends OneToOneCategory<RecipeHolder<R>> {
private final RecipeType<HammerRecipe> recipeType; private final RecipeType<RecipeHolder<R>> recipeType;
public HammerCategory(IGuiHelper helper, IDrawable arrow, Supplier<? extends Item> icon, Component title, RecipeType<HammerRecipe> recipeType) { public HammerCategory(IGuiHelper helper, IDrawable arrow, Supplier<? extends Item> icon, Component title, RecipeType<RecipeHolder<R>> recipeType) {
super(helper, arrow, helper.createDrawableItemStack(new ItemStack(icon.get())), title); super(helper, arrow, helper.createDrawableItemStack(new ItemStack(icon.get())), title);
this.recipeType = recipeType; this.recipeType = recipeType;
} }
@Override @Override
public RecipeType<HammerRecipe> getRecipeType() { public RecipeType<RecipeHolder<R>> getRecipeType() {
return this.recipeType; return this.recipeType;
} }
@Override @Override
protected void addInput(IRecipeSlotBuilder slot, HammerRecipe recipe) { protected void addInput(IRecipeSlotBuilder slot, RecipeHolder<R> holder) {
slot.addIngredients(recipe.ingredient()); slot.addIngredients(holder.value().ingredient());
} }
@Override @Override
protected void addOutput(IRecipeSlotBuilder slot, HammerRecipe recipe) { protected void addOutput(IRecipeSlotBuilder slot, RecipeHolder<R> holder) {
var recipe = holder.value();
if (recipe.resultAmount instanceof ConstantValue constant) { if (recipe.resultAmount instanceof ConstantValue constant) {
slot.addItemStack(recipe.result.getCount() == 1 ? recipe.result : recipe.result.copyWithCount((int) constant.value())); slot.addItemStack(recipe.result.getCount() == 1 ? recipe.result : recipe.result.copyWithCount((int) constant.value()));
} else { } else {

View File

@ -21,32 +21,60 @@ package thedarkcolour.exdeorum.compat.jei;
import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder;
import mezz.jei.api.gui.builder.IRecipeSlotBuilder; import mezz.jei.api.gui.builder.IRecipeSlotBuilder;
import mezz.jei.api.gui.builder.ITooltipBuilder;
import mezz.jei.api.gui.drawable.IDrawable; import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback;
import mezz.jei.api.gui.ingredient.IRecipeSlotView;
import mezz.jei.api.gui.ingredient.IRecipeSlotsView; import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
import mezz.jei.api.helpers.IGuiHelper; import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.helpers.IJeiHelpers;
import mezz.jei.api.helpers.IModIdHelper;
import mezz.jei.api.ingredients.IIngredientHelper;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.recipe.IFocusGroup; import mezz.jei.api.recipe.IFocusGroup;
import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeIngredientRole;
import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.RecipeType;
import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.recipe.category.IRecipeCategory;
import mezz.jei.api.runtime.IIngredientManager;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.compat.XeiSieveRecipe; import thedarkcolour.exdeorum.compat.XeiSieveRecipe;
import thedarkcolour.exdeorum.compat.XeiUtil; import thedarkcolour.exdeorum.compat.XeiUtil;
import thedarkcolour.exdeorum.data.TranslationKeys; import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.material.DefaultMaterials; import thedarkcolour.exdeorum.material.DefaultMaterials;
import java.util.Optional;
class SieveCategory implements IRecipeCategory<XeiSieveRecipe> { class SieveCategory implements IRecipeCategory<XeiSieveRecipe> {
private final IJeiHelpers jeiHelpers;
private final IDrawable slot; private final IDrawable slot;
private final IDrawable row; private final IDrawable row;
private final IDrawable icon; private final IDrawable icon;
private final Component title; private final Component title;
private final MutableInt rows; private final MutableInt rows;
SieveCategory(IGuiHelper helper, ItemLike icon, Component title, MutableInt rows) { /**
* settingSlots is a workaround to prevent JEI from adding recipe tooltips with our generated recipe IDs.
* They look ugly and clutter the screen, since they're pretty long.
*/
private boolean settingSlots = false;
SieveCategory(IJeiHelpers jeiHelpers, ItemLike icon, Component title, MutableInt rows) {
this.jeiHelpers = jeiHelpers;
var helper = jeiHelpers.getGuiHelper();
this.slot = helper.getSlotDrawable(); this.slot = helper.getSlotDrawable();
this.row = helper.createDrawable(ExDeorumJeiPlugin.EX_DEORUM_JEI_TEXTURE, 0, 0, 162, 18); this.row = helper.createDrawable(ExDeorumJeiPlugin.EX_DEORUM_JEI_TEXTURE, 0, 0, 162, 18);
this.icon = helper.createDrawableItemStack(new ItemStack(icon)); this.icon = helper.createDrawableItemStack(new ItemStack(icon));
@ -54,8 +82,8 @@ class SieveCategory implements IRecipeCategory<XeiSieveRecipe> {
this.rows = rows; this.rows = rows;
} }
SieveCategory(IGuiHelper helper) { SieveCategory(IJeiHelpers jeiHelpers) {
this(helper, DefaultMaterials.OAK_SIEVE, Component.translatable(TranslationKeys.SIEVE_CATEGORY_TITLE), XeiSieveRecipe.SIEVE_ROWS); this(jeiHelpers, DefaultMaterials.OAK_SIEVE, Component.translatable(TranslationKeys.SIEVE_CATEGORY_TITLE), XeiSieveRecipe.SIEVE_ROWS);
} }
@Override @Override
@ -83,17 +111,26 @@ class SieveCategory implements IRecipeCategory<XeiSieveRecipe> {
return this.icon; return this.icon;
} }
@Override
public @Nullable ResourceLocation getRegistryName(XeiSieveRecipe recipe) {
return settingSlots ? null : recipe.id();
}
@Override @Override
public void setRecipe(IRecipeLayoutBuilder builder, XeiSieveRecipe recipe, IFocusGroup focuses) { public void setRecipe(IRecipeLayoutBuilder builder, XeiSieveRecipe recipe, IFocusGroup focuses) {
builder.addSlot(RecipeIngredientRole.INPUT, 59, 1).addIngredients(recipe.ingredient()); builder.addSlot(RecipeIngredientRole.INPUT, 59, 1).addIngredients(recipe.ingredient());
builder.addSlot(RecipeIngredientRole.CATALYST, 87, 1).addItemStack(recipe.mesh()); builder.addSlot(RecipeIngredientRole.CATALYST, 87, 1).addItemStack(recipe.mesh());
settingSlots = true;
for (int i = 0; i < recipe.results().size(); i++) { for (int i = 0; i < recipe.results().size(); i++) {
var result = recipe.results().get(i); var result = recipe.results().get(i);
var slot = builder.addSlot(RecipeIngredientRole.OUTPUT, 1 + (i % 9) * 18, 1 + XeiUtil.SIEVE_ROW_START + 18 * (i / 9)).addItemStack(result.item); var slot = builder.addSlot(RecipeIngredientRole.OUTPUT, 1 + (i % 9) * 18, 1 + XeiUtil.SIEVE_ROW_START + 18 * (i / 9)).addItemStack(result.item);
// Since we group recipes, we need to manually add the recipe ID tooltip for the given output item.
slot.addRichTooltipCallback(new OutputSlotTooltipCallback(result.holder.id(), getRecipeType()));
addTooltips(slot, result.byHandOnly, result.provider); addTooltips(slot, result.byHandOnly, result.provider);
} }
settingSlots = false;
} }
public static void addTooltips(IRecipeSlotBuilder slot, boolean byHandOnly, NumberProvider provider) { public static void addTooltips(IRecipeSlotBuilder slot, boolean byHandOnly, NumberProvider provider) {
@ -116,4 +153,77 @@ class SieveCategory implements IRecipeCategory<XeiSieveRecipe> {
this.row.draw(graphics, 0, 28 + i * 18); this.row.draw(graphics, 0, 28 + i * 18);
} }
} }
/**
* Copied almost 1:1 from class of same name in JEI. Since this is a JEI implementation detail, it's not exposed in the API.
* Modified minimally to not use any of the other internals and compile.
*/
public class OutputSlotTooltipCallback implements IRecipeSlotRichTooltipCallback {
private static final Logger LOGGER = LogManager.getLogger();
private final ResourceLocation recipeName;
private final boolean recipeFromSameModAsCategory;
public OutputSlotTooltipCallback(ResourceLocation recipeName, RecipeType<?> recipeType) {
this.recipeName = recipeName;
this.recipeFromSameModAsCategory = recipeName.getNamespace().equals(recipeType.getUid().getNamespace());
}
@Override
public void onRichTooltip(IRecipeSlotView recipeSlotView, ITooltipBuilder tooltip) {
if (recipeSlotView.getRole() != RecipeIngredientRole.OUTPUT) {
return;
}
Optional<ITypedIngredient<?>> displayedIngredient = recipeSlotView.getDisplayedIngredient();
if (displayedIngredient.isEmpty()) {
return;
}
addRecipeBy(tooltip, displayedIngredient.get());
Minecraft minecraft = Minecraft.getInstance();
boolean showAdvanced = minecraft.options.advancedItemTooltips || Screen.hasShiftDown();
if (showAdvanced) {
MutableComponent recipeId = Component.translatable("jei.tooltip.recipe.id", Component.literal(recipeName.toString()));
tooltip.add(recipeId.withStyle(ChatFormatting.DARK_GRAY));
}
}
private void addRecipeBy(ITooltipBuilder tooltip, ITypedIngredient<?> displayedIngredient) {
if (recipeFromSameModAsCategory) {
return;
}
IModIdHelper modIdHelper = jeiHelpers.getModIdHelper();
if (!modIdHelper.isDisplayingModNameEnabled()) {
return;
}
String ingredientModId = getDisplayModId(displayedIngredient);
if (ingredientModId == null) {
return;
}
String recipeModId = recipeName.getNamespace();
if (recipeModId.equals(ingredientModId)) {
return;
}
String modName = modIdHelper.getFormattedModNameForModId(recipeModId);
MutableComponent recipeBy = Component.translatable("jei.tooltip.recipe.by", modName);
tooltip.add(recipeBy.withStyle(ChatFormatting.GRAY));
}
private <T> @Nullable String getDisplayModId(ITypedIngredient<T> typedIngredient) {
IIngredientManager ingredientManager = jeiHelpers.getIngredientManager();
IIngredientType<T> type = typedIngredient.getType();
T ingredient = typedIngredient.getIngredient();
IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(type);
try {
return ingredientHelper.getDisplayModId(ingredient);
} catch (RuntimeException e) {
String ingredientInfo = ingredientHelper.getErrorInfo(ingredient);
LOGGER.error("Caught exception from ingredient without a resource location: {}", ingredientInfo, e);
return null;
}
}
}
} }