ExtendedAE_Plus/src/main/java/com/extendedae_plus/util/RecipeFinderUtil.java
2026-02-28 15:34:18 +08:00

211 lines
8.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.extendedae_plus.util;
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
import mezz.jei.api.constants.RecipeTypes;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.gui.IRecipeLayoutDrawable;
import mezz.jei.api.gui.ingredient.IRecipeSlotView;
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
import mezz.jei.api.helpers.IJeiHelpers;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.recipe.IFocus;
import mezz.jei.api.recipe.IFocusFactory;
import mezz.jei.api.recipe.IRecipeManager;
import mezz.jei.api.recipe.RecipeIngredientRole;
import mezz.jei.api.recipe.category.IRecipeCategory;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
import net.minecraft.world.item.crafting.Recipe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 配方查找工具类
*
* <p>使用 JEI API 根据物品查找相关配方,返回包含完整数量信息的 RecipeInfo</p>
*/
public class RecipeFinderUtil {
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder");
/**
* 根据JEI物品查找相关配方仅搜索以该物品为输出的配方
*
* @param ingredient JEI物品
* @return 相关配方信息列表(包含完整的输入输出数量)
*/
public static List<RecipeInfo> findRecipesByIngredient(ITypedIngredient<?> ingredient) {
// 获取 JEI Runtime
IJeiRuntime jeiRuntime = JeiRuntimeProxy.get();
if (jeiRuntime == null) {
LOGGER.warn("[RecipeFinder] JEI Runtime not available");
return List.of();
}
// 只支持物品类型
if (ingredient.getType() != VanillaTypes.ITEM_STACK) {
LOGGER.warn("[RecipeFinder] Unsupported ingredient type: {}", ingredient.getType());
// TODO: Support fluids, chemicals, and other AE2-compatible types
return List.of();
}
IJeiHelpers jeiHelpers = jeiRuntime.getJeiHelpers();
IRecipeManager recipeManager = jeiRuntime.getRecipeManager();
IFocusFactory focusFactory = jeiHelpers.getFocusFactory();
// 创建输出焦点OUTPUT role
IFocus<?> outputFocus = focusFactory.createFocus(
RecipeIngredientRole.OUTPUT,
ingredient
);
List<RecipeInfo> results = new ArrayList<>();
// 查找工作台配方
try {
IRecipeCategory<CraftingRecipe> craftingCategory = recipeManager.getRecipeCategory(RecipeTypes.CRAFTING);
recipeManager.createRecipeLookup(RecipeTypes.CRAFTING)
.limitFocus(List.of(outputFocus))
.get()
.forEach(recipe -> {
// 创建配方布局以获取完整信息
Optional<IRecipeLayoutDrawable<CraftingRecipe>> layoutOpt =
recipeManager.createRecipeLayoutDrawable(
craftingCategory,
recipe,
focusFactory.getEmptyFocusGroup()
);
layoutOpt.ifPresent(layout -> {
RecipeInfo info = extractRecipeInfo(recipe, layout, true);
if (info != null) {
results.add(info);
}
});
});
} catch (Exception e) {
LOGGER.warn("[RecipeFinder] Error searching crafting recipes: {}", e.getMessage());
}
// 查找其他所有配方类型(排除工作台配方)
try {
jeiHelpers.getAllRecipeTypes().forEach(recipeType -> {
// 跳过工作台配方(已经处理过)
if (recipeType.equals(RecipeTypes.CRAFTING)) {
return;
}
try {
@SuppressWarnings("unchecked")
IRecipeCategory<Recipe<?>> category = (IRecipeCategory<Recipe<?>>) recipeManager.getRecipeCategory(recipeType);
recipeManager.createRecipeLookup(recipeType)
.limitFocus(List.of(outputFocus))
.get()
.forEach(recipe -> {
if (recipe instanceof Recipe<?> rawRecipe) {
// 创建配方布局以获取完整信息
Optional<IRecipeLayoutDrawable<Recipe<?>>> layoutOpt =
recipeManager.createRecipeLayoutDrawable(
category,
rawRecipe,
focusFactory.getEmptyFocusGroup()
);
layoutOpt.ifPresent(layout -> {
RecipeInfo info = extractRecipeInfo(rawRecipe, layout, false);
if (info != null) {
results.add(info);
}
});
}
});
} catch (Exception e) {
// 某些配方类型可能不支持,静默忽略
}
});
} catch (Exception e) {
LOGGER.warn("[RecipeFinder] Error searching other recipe types: {}", e.getMessage());
}
LOGGER.debug("[RecipeFinder] Found {} recipes for output: {}",
results.size(),
((ItemStack) ingredient.getIngredient()).getDescriptionId());
return results;
}
/**
* 从配方布局中提取完整的配方信息
*
* @param recipe 原始配方对象
* @param layout JEI 配方布局(包含完整的槽位和数量信息)
* @param isCrafting 是否为工作台配方
* @return 配方信息,如果提取失败返回 null
*/
private static <T> RecipeInfo extractRecipeInfo(
Recipe<?> recipe,
IRecipeLayoutDrawable<T> layout,
boolean isCrafting
) {
try {
IRecipeSlotsView slotsView = layout.getRecipeSlotsView();
// 提取输入槽位
List<IRecipeSlotView> inputSlots = slotsView.getSlotViews(RecipeIngredientRole.INPUT);
List<List<ItemStack>> inputs = new ArrayList<>();
for (IRecipeSlotView slot : inputSlots) {
List<ItemStack> slotItems = slot.getItemStacks()
.map(ItemStack::copy) // 复制以保留数量信息
.toList();
inputs.add(slotItems);
}
// 提取输出槽位
List<IRecipeSlotView> outputSlots = slotsView.getSlotViews(RecipeIngredientRole.OUTPUT);
List<ItemStack> outputs = new ArrayList<>();
for (IRecipeSlotView slot : outputSlots) {
slot.getItemStacks()
.map(ItemStack::copy) // 复制以保留数量信息
.forEach(outputs::add);
}
return new RecipeInfo(recipe, isCrafting, inputs, outputs);
} catch (Exception e) {
LOGGER.warn("[RecipeFinder] Failed to extract recipe info for {}: {}",
recipe.getId(), e.getMessage());
return null;
}
}
/**
* 选择最佳配方(优先选择工作台配方)
*
* @param recipes 配方信息列表
* @return 最佳配方信息如果列表为空返回null
*/
public static RecipeInfo selectBestRecipe(List<RecipeInfo> recipes) {
if (recipes.isEmpty()) {
return null;
}
// 优先返回工作台配方
for (RecipeInfo info : recipes) {
if (info.isCraftingRecipe()) {
return info;
}
}
// 没有工作台配方,返回第一个
return recipes.get(0);
}
}