From 8aeeb1e226f3ffa4fcdf3ad8e08b15eba4cfa23e Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:35:25 +0800 Subject: [PATCH] =?UTF-8?q?jei=E8=BF=90=E8=A1=8C=E6=97=B6=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/jei/JeiBookmarkBridge.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java diff --git a/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java b/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java new file mode 100644 index 0000000..9e1572d --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java @@ -0,0 +1,181 @@ +package com.extendedae_plus.integration.jei; + +import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.forge.ForgeTypes; +import mezz.jei.api.ingredients.IIngredientType; +import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.runtime.IBookmarkOverlay; +import mezz.jei.api.runtime.IJeiRuntime; +import mezz.jei.gui.bookmarks.BookmarkList; +import mezz.jei.gui.bookmarks.IngredientBookmark; +import mezz.jei.gui.overlay.elements.IElement; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.ModList; +import org.spongepowered.asm.mixin.Pseudo; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * 将所有会引用 JEI GUI 内部类(如 BookmarkList、IngredientBookmark、IElement 等)的逻辑 + * 隔离在此桥接类中,避免在未安装 JEI 或 JEI 组件不完整时过早类加载导致的 NoClassDefFoundError。 + * + * 该类仅会被 {@link JeiRuntimeProxy} 通过反射调用。 + */ +@Pseudo +public final class JeiBookmarkBridge { + private JeiBookmarkBridge() {} + + // 通过 JeiRuntimeProxy 的包内可见方法安全地获取 Runtime + private static @Nullable IJeiRuntime getRuntime() { + try { + Class proxy = Class.forName("com.extendedae_plus.integration.jei.JeiRuntimeProxy"); + var m = proxy.getDeclaredMethod("get"); + Object rt = m.invoke(null); + return (IJeiRuntime) rt; + } catch (Throwable ignored) { + return null; + } + } + + public static List> getBookmarkList() { + IJeiRuntime rt = getRuntime(); + if (rt == null) return Collections.emptyList(); + IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); + if (bookmarkOverlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList bookmarkList = accessor.eap$getBookmarkList(); + return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList(); + } + return Collections.emptyList(); + } + + public static void addBookmark(ItemStack stack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.add(bookmark); // add 内部会自动保存到配置 + }); + } + } + + public static void addBookmark(FluidStack fluidStack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.add(bookmark); // add 内部会自动保存到配置 + }); + } + } + + /** + * 如果存在 Mekanism/appmek,则将 Mekanism 化学堆栈添加到 JEI 书签。 + */ + public static void addBookmark(Object chemicalStack) { + if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return; + + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + try { + if (chemicalStack == null) return; + + // Determine Mekanism JEI ingredient type constant by runtime class name + String clsName = chemicalStack.getClass().getName(); + String mekanismJeiClass = "mekanism.client.jei.MekanismJEI"; + Class jeiCls = Class.forName(mekanismJeiClass); + Field typeField = getField(clsName, jeiCls); + + if (typeField == null) return; + Object typeConst = typeField.get(null); + + // Use ingredient manager reflectively to create a typed ingredient + Object ingredientManager = rt.getIngredientManager(); + Method createTypedIngredient = ingredientManager.getClass().getMethod("createTypedIngredient", IIngredientType.class, Object.class); + Object opt = createTypedIngredient.invoke(ingredientManager, typeConst, chemicalStack); + if (!(opt instanceof Optional typedOpt)) return; + if (typedOpt.isPresent()) { + Object typed = typedOpt.get(); + // Find a compatible static create(...) method on IngredientBookmark where + // the second parameter is assignable from the actual ingredientManager instance. + Method createMethod = null; + for (Method m : IngredientBookmark.class.getMethods()) { + if (!m.getName().equals("create")) continue; + Class[] params = m.getParameterTypes(); + if (params.length != 2) continue; + // first param should accept the typed ingredient + boolean firstOk = params[0].isAssignableFrom(typed.getClass()) || params[0].isAssignableFrom(ITypedIngredient.class); + boolean secondOk = params[1].isAssignableFrom(ingredientManager.getClass()); + if (firstOk && secondOk) { + createMethod = m; + break; + } + } + if (createMethod != null) { + Object bookmark = createMethod.invoke(null, typed, ingredientManager); + if (bookmark != null) { + list.add((IngredientBookmark) bookmark); + } + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private static @Nullable Field getField(String clsName, Class jeiCls) throws NoSuchFieldException { + Field typeField = null; + if ("mekanism.api.chemical.gas.GasStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_GAS"); + } else if ("mekanism.api.chemical.infuse.InfusionStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_INFUSION"); + } else if ("mekanism.api.chemical.pigment.PigmentStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_PIGMENT"); + } else if ("mekanism.api.chemical.slurry.SlurryStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_SLURRY"); + } + return typeField; + } + + /** + * 从 JEI 书签移除物品 + */ + public static void removeBookmark(ItemStack stack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.remove(bookmark); + }); + } + } +}