diff --git a/build.gradle b/build.gradle index 869790c..824b58b 100644 --- a/build.gradle +++ b/build.gradle @@ -151,7 +151,7 @@ dependencies { compileOnly "curse.maven:framedblocks-441647:6127891" compileOnly "curse.maven:advancedae-1084104:6225993" compileOnly "curse.maven:functional-storage-556861:6467726" - compileOnly "mezz.jei:jei-1.21.1-neoforge:19.21.0.247" + compileOnly "mezz.jei:jei-1.21.1-neoforge:19.27.0.340" compileOnly "curse.maven:ae2-jei-integration-1074338:5748513" // runtime test @@ -194,7 +194,7 @@ dependencies { runtimeOnly "curse.maven:cloth-config-348521:5424576" break case 'jei': - runtimeOnly "mezz.jei:jei-1.21.1-neoforge:19.21.0.247" + runtimeOnly "mezz.jei:jei-1.21.1-neoforge:19.27.0.340" runtimeOnly "curse.maven:ae2-jei-integration-1074338:5748513" break } diff --git a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java index ff55ca3..79db86c 100644 --- a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java +++ b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java @@ -7,6 +7,7 @@ import net.neoforged.neoforge.fluids.FluidStack; import javax.annotation.Nullable; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -34,45 +35,66 @@ public final class JeiRuntimeProxy { } public static Optional getIngredientUnderMouse() { - Object rt = RUNTIME; - if (rt == null) return Optional.empty(); - try { - Method getIngredientListOverlay = rt.getClass().getMethod("getIngredientListOverlay"); - Object list = getIngredientListOverlay.invoke(rt); - if (list != null) { - Method m = list.getClass().getMethod("getIngredientUnderMouse"); - Object opt = m.invoke(list); - if (opt instanceof Optional o && o.isPresent()) return o; - } - Method getBookmarkOverlay = rt.getClass().getMethod("getBookmarkOverlay"); - Object bm = getBookmarkOverlay.invoke(rt); - if (bm != null) { - Method m = bm.getClass().getMethod("getIngredientUnderMouse"); - Object opt = m.invoke(bm); - if (opt instanceof Optional o && o.isPresent()) return o; - } - } catch (Throwable ignored) {} - return Optional.empty(); + return getIngredientUnderMouse(getGuiMouseX(), getGuiMouseY()); } public static Optional getIngredientUnderMouse(double mouseX, double mouseY) { Object rt = RUNTIME; - if (rt == null) return Optional.empty(); + if (rt == null) { + return Optional.empty(); + } + + Optional ingredient = getRuntimeComponent(rt, "getIngredientListOverlay") + .flatMap(overlay -> getTypedIngredientFromClickableSource(overlay, mouseX, mouseY)); + if (ingredient.isPresent()) { + return ingredient; + } + + Optional bookmarkOverlay = getRuntimeComponent(rt, "getBookmarkOverlay"); + ingredient = bookmarkOverlay.flatMap(overlay -> getTypedIngredientFromClickableSource(overlay, mouseX, mouseY)); + if (ingredient.isPresent()) { + return ingredient; + } + + ingredient = getRuntimeComponent(rt, "getIngredientListOverlay") + .flatMap(overlay -> invokeOptionalValue(overlay, "getIngredientUnderMouse")); + if (ingredient.isPresent()) { + return ingredient; + } + + ingredient = bookmarkOverlay.flatMap(overlay -> invokeOptionalValue(overlay, "getIngredientUnderMouse")); + if (ingredient.isPresent()) { + return ingredient; + } + + Optional bookmark = bookmarkOverlay.flatMap(overlay -> getBookmarkUnderMouse(overlay, mouseX, mouseY)); + if (bookmark.isPresent()) { + ingredient = getTypedIngredientFromBookmark(bookmark.get()); + if (ingredient.isPresent()) { + return ingredient; + } + } + try { Class ingredientTypeClass = eap$getIngredientTypeClass(); Method getRecipesGui = rt.getClass().getMethod("getRecipesGui"); Object gui = getRecipesGui.invoke(rt); - if (gui == null) return Optional.empty(); + if (gui == null) { + return Optional.empty(); + } Object ingredientManager = rt.getClass().getMethod("getIngredientManager").invoke(rt); Class vanillaTypes = Class.forName("mezz.jei.api.constants.VanillaTypes"); Object itemType = vanillaTypes.getField("ITEM_STACK").get(null); Method getUnder = gui.getClass().getMethod("getIngredientUnderMouse", ingredientTypeClass); Object valueOpt = getUnder.invoke(gui, itemType); - if (!(valueOpt instanceof Optional value) || value.isEmpty()) return Optional.empty(); + if (!(valueOpt instanceof Optional value) || value.isEmpty()) { + return Optional.empty(); + } Method createTyped = ingredientManager.getClass().getMethod("createTypedIngredient", ingredientTypeClass, Object.class); Object typedOpt = createTyped.invoke(ingredientManager, itemType, value.get()); return typedOpt instanceof Optional o ? o : Optional.empty(); - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } return Optional.empty(); } @@ -90,7 +112,9 @@ public final class JeiRuntimeProxy { public static String getTypedIngredientDisplayName(Object typed) { Object rt = RUNTIME; - if (rt == null || typed == null) return ""; + if (rt == null || typed == null) { + return ""; + } try { Class ingredientTypeClass = eap$getIngredientTypeClass(); Object manager = rt.getClass().getMethod("getIngredientManager").invoke(rt); @@ -101,7 +125,9 @@ public final class JeiRuntimeProxy { Method getIngredient = typed.getClass().getMethod("getIngredient"); Object ingredient = getIngredient.invoke(typed); Object display = helper.getClass().getMethod("getDisplayName", ingredient.getClass()).invoke(helper, ingredient); - if (display == null) return ""; + if (display == null) { + return ""; + } try { Class comp = Class.forName("net.minecraft.network.chat.Component"); if (comp.isInstance(display)) { @@ -109,18 +135,24 @@ public final class JeiRuntimeProxy { Object s = getString.invoke(display); return s == null ? "" : s.toString(); } - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } return display.toString(); - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } return ""; } public static List getBookmarkList() { Object rt = RUNTIME; - if (rt == null) return Collections.emptyList(); + if (rt == null) { + return Collections.emptyList(); + } try { Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return Collections.emptyList(); + if (overlay == null) { + return Collections.emptyList(); + } try { Field f = overlay.getClass().getDeclaredField("bookmarkList"); f.setAccessible(true); @@ -128,16 +160,22 @@ public final class JeiRuntimeProxy { Method getElements = list.getClass().getMethod("getElements"); Object elements = getElements.invoke(list); if (elements instanceof List l) { - // map(IElement::getTypedIngredient) try { Method getTyped = Class.forName("mezz.jei.gui.overlay.elements.IElement").getMethod("getTypedIngredient"); return l.stream().map(e -> { - try { return getTyped.invoke(e); } catch (Throwable ignored) { return null; } + try { + return getTyped.invoke(e); + } catch (Throwable ignored) { + return null; + } }).filter(x -> x != null).toList(); - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } } - } catch (Throwable ignored) {} - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } + } catch (Throwable ignored) { + } return Collections.emptyList(); } @@ -145,76 +183,22 @@ public final class JeiRuntimeProxy { * 获取鼠标下的配方书签(RecipeBookmark),用于 Ctrl+Q 直接识别“配方书签”而非普通配料。 */ public static Optional getRecipeBookmarkUnderMouse() { - Object rt = RUNTIME; - if (rt == null) return Optional.empty(); - try { - Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return Optional.empty(); - - // JEI 1.21 优先路径:直接拿鼠标下 clickable element 的 bookmark - try { - double mouseX = getGuiMouseX(); - double mouseY = getGuiMouseY(); - Object clickableStream = overlay.getClass() - .getMethod("getIngredientUnderMouse", double.class, double.class) - .invoke(overlay, mouseX, mouseY); - if (clickableStream instanceof java.util.stream.Stream stream) { - Object firstClickable = stream.findFirst().orElse(null); - if (firstClickable != null) { - Object element = firstClickable.getClass().getMethod("getElement").invoke(firstClickable); - if (element != null) { - Object bookmarkOpt = element.getClass().getMethod("getBookmark").invoke(element); - if (bookmarkOpt instanceof Optional b && b.isPresent()) { - Object bookmark = b.get(); - if (bookmark != null && "RecipeBookmark".equals(bookmark.getClass().getSimpleName())) { - return Optional.of(bookmark); - } - } - } - } - } - } catch (Throwable ignored) { - } - - // 兼容回退:基于 typed ingredient 匹配 bookmarkList 元素 - Object ingredientOpt = overlay.getClass().getMethod("getIngredientUnderMouse").invoke(overlay); - if (!(ingredientOpt instanceof Optional opt) || opt.isEmpty()) return Optional.empty(); - Object hoveredIngredient = opt.get(); - - Field f = overlay.getClass().getDeclaredField("bookmarkList"); - f.setAccessible(true); - Object bookmarkList = f.get(overlay); - if (bookmarkList == null) return Optional.empty(); - - Object elementsObj = bookmarkList.getClass().getMethod("getElements").invoke(bookmarkList); - if (!(elementsObj instanceof List elements)) return Optional.empty(); - - for (Object element : elements) { - if (element == null) continue; - Object typedIngredient = null; - try { - typedIngredient = element.getClass().getMethod("getTypedIngredient").invoke(element); - } catch (Throwable ignored) { - } - if (typedIngredient == null || !typedIngredient.equals(hoveredIngredient)) continue; - - Object bookmarkOpt = null; - try { - bookmarkOpt = element.getClass().getMethod("getBookmark").invoke(element); - } catch (Throwable ignored) { - } - if (bookmarkOpt instanceof Optional b && b.isPresent()) { - Object bookmark = b.get(); - if (bookmark != null && "RecipeBookmark".equals(bookmark.getClass().getSimpleName())) { - return Optional.of(bookmark); - } - } - } - } catch (Throwable ignored) { + Optional bookmark = getBookmarkUnderMouse(); + if (bookmark.isPresent() && isRecipeBookmark(bookmark.get())) { + return bookmark; } return Optional.empty(); } + public static Optional getBookmarkUnderMouse() { + Object rt = RUNTIME; + if (rt == null) { + return Optional.empty(); + } + return getRuntimeComponent(rt, "getBookmarkOverlay") + .flatMap(overlay -> getBookmarkUnderMouse(overlay, getGuiMouseX(), getGuiMouseY())); + } + private static double getGuiMouseX() { try { Class mcCls = Class.forName("net.minecraft.client.Minecraft"); @@ -225,7 +209,9 @@ public final class JeiRuntimeProxy { double xpos = (double) mouseHandler.getClass().getMethod("xpos").invoke(mouseHandler); int guiW = (int) window.getClass().getMethod("getGuiScaledWidth").invoke(window); int screenW = (int) window.getClass().getMethod("getScreenWidth").invoke(window); - if (screenW <= 0) return xpos; + if (screenW <= 0) { + return xpos; + } return xpos * ((double) guiW / (double) screenW); } catch (Throwable ignored) { return 0.0D; @@ -242,7 +228,9 @@ public final class JeiRuntimeProxy { double ypos = (double) mouseHandler.getClass().getMethod("ypos").invoke(mouseHandler); int guiH = (int) window.getClass().getMethod("getGuiScaledHeight").invoke(window); int screenH = (int) window.getClass().getMethod("getScreenHeight").invoke(window); - if (screenH <= 0) return ypos; + if (screenH <= 0) { + return ypos; + } return ypos * ((double) guiH / (double) screenH); } catch (Throwable ignored) { return 0.0D; @@ -251,10 +239,14 @@ public final class JeiRuntimeProxy { public static void addBookmark(ItemStack stack) { Object rt = RUNTIME; - if (rt == null || stack == null || stack.isEmpty()) return; + if (rt == null || stack == null || stack.isEmpty()) { + return; + } try { Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return; + if (overlay == null) { + return; + } Field f = overlay.getClass().getDeclaredField("bookmarkList"); f.setAccessible(true); Object list = f.get(overlay); @@ -269,7 +261,8 @@ public final class JeiRuntimeProxy { Method create = null; for (Method m : ibCls.getMethods()) { if (m.getName().equals("create") && m.getParameterCount() == 2) { - create = m; break; + create = m; + break; } } if (create != null) { @@ -277,15 +270,20 @@ public final class JeiRuntimeProxy { list.getClass().getMethod("add", ibCls).invoke(list, bookmark); } } - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } } public static void addBookmark(FluidStack fluidStack) { Object rt = RUNTIME; - if (rt == null) return; + if (rt == null) { + return; + } try { Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return; + if (overlay == null) { + return; + } Field f = overlay.getClass().getDeclaredField("bookmarkList"); f.setAccessible(true); Object list = f.get(overlay); @@ -299,23 +297,33 @@ public final class JeiRuntimeProxy { Class ibCls = Class.forName("mezz.jei.gui.bookmarks.IngredientBookmark"); Method create = null; for (Method m : ibCls.getMethods()) { - if (m.getName().equals("create") && m.getParameterCount() == 2) { create = m; break; } + if (m.getName().equals("create") && m.getParameterCount() == 2) { + create = m; + break; + } } if (create != null) { Object bookmark = create.invoke(null, typed, manager); list.getClass().getMethod("add", ibCls).invoke(list, bookmark); } } - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } } public static void addBookmark(Object chemicalStack) { - if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return; + if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) { + return; + } Object rt = RUNTIME; - if (rt == null || chemicalStack == null) return; + if (rt == null || chemicalStack == null) { + return; + } try { Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return; + if (overlay == null) { + return; + } Field f = overlay.getClass().getDeclaredField("bookmarkList"); f.setAccessible(true); Object list = f.get(overlay); @@ -327,7 +335,9 @@ public final class JeiRuntimeProxy { if ("mekanism.api.chemical.ChemicalStack".equals(chemicalStack.getClass().getName())) { typeField = jeiCls.getField("TYPE_CHEMICAL"); } - if (typeField == null) return; + if (typeField == null) { + return; + } Object typeConst = typeField.get(null); Method createTyped = manager.getClass().getMethod("createTypedIngredient", ingredientTypeClass, Object.class); Object typedOpt = createTyped.invoke(manager, typeConst, chemicalStack); @@ -336,22 +346,30 @@ public final class JeiRuntimeProxy { Class ibCls = Class.forName("mezz.jei.gui.bookmarks.IngredientBookmark"); Method create = null; for (Method m : ibCls.getMethods()) { - if (m.getName().equals("create") && m.getParameterCount() == 2) { create = m; break; } + if (m.getName().equals("create") && m.getParameterCount() == 2) { + create = m; + break; + } } if (create != null) { Object bookmark = create.invoke(null, typed, manager); list.getClass().getMethod("add", ibCls).invoke(list, bookmark); } } - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } } public static void removeBookmark(ItemStack stack) { Object rt = RUNTIME; - if (rt == null || stack == null || stack.isEmpty()) return; + if (rt == null || stack == null || stack.isEmpty()) { + return; + } try { Object overlay = rt.getClass().getMethod("getBookmarkOverlay").invoke(rt); - if (overlay == null) return; + if (overlay == null) { + return; + } Field f = overlay.getClass().getDeclaredField("bookmarkList"); f.setAccessible(true); Object list = f.get(overlay); @@ -364,13 +382,224 @@ public final class JeiRuntimeProxy { Class ibCls = Class.forName("mezz.jei.gui.bookmarks.IngredientBookmark"); Method create = null; for (Method m : ibCls.getMethods()) { - if (m.getName().equals("create") && m.getParameterCount() == 2) { create = m; break; } + if (m.getName().equals("create") && m.getParameterCount() == 2) { + create = m; + break; + } } if (create != null) { Object bookmark = create.invoke(null, typed, manager); list.getClass().getMethod("remove", ibCls).invoke(list, bookmark); } } - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } + } + + private static Optional getRuntimeComponent(Object runtime, String methodName) { + if (runtime == null) { + return Optional.empty(); + } + try { + Object value = runtime.getClass().getMethod(methodName).invoke(runtime); + return Optional.ofNullable(value); + } catch (Throwable ignored) { + return Optional.empty(); + } + } + + private static Optional invokeOptionalValue(Object owner, String methodName) { + if (owner == null) { + return Optional.empty(); + } + try { + Object value = owner.getClass().getMethod(methodName).invoke(owner); + if (value instanceof Optional optional && optional.isPresent()) { + return Optional.ofNullable(optional.get()); + } + } catch (Throwable ignored) { + } + return Optional.empty(); + } + + private static Optional getClickableIngredientUnderMouse(Object owner, double mouseX, double mouseY) { + if (owner == null) { + return Optional.empty(); + } + try { + Object streamObj = owner.getClass() + .getMethod("getIngredientUnderMouse", double.class, double.class) + .invoke(owner, mouseX, mouseY); + if (streamObj instanceof java.util.stream.Stream stream) { + return Optional.ofNullable(stream.findFirst().orElse(null)); + } + } catch (Throwable ignored) { + } + return Optional.empty(); + } + + private static Optional getTypedIngredientFromClickableSource(Object owner, double mouseX, double mouseY) { + return getClickableIngredientUnderMouse(owner, mouseX, mouseY) + .flatMap(JeiRuntimeProxy::getElementFromClickable) + .flatMap(JeiRuntimeProxy::getTypedIngredientFromElement); + } + + private static Optional getBookmarkUnderMouse(Object overlay, double mouseX, double mouseY) { + Optional bookmark = getClickableIngredientUnderMouse(overlay, mouseX, mouseY) + .flatMap(JeiRuntimeProxy::getElementFromClickable) + .flatMap(JeiRuntimeProxy::getBookmarkFromElement); + if (bookmark.isPresent()) { + return bookmark; + } + + Optional hoveredIngredient = getTypedIngredientFromClickableSource(overlay, mouseX, mouseY); + if (hoveredIngredient.isEmpty()) { + hoveredIngredient = invokeOptionalValue(overlay, "getIngredientUnderMouse"); + } + if (hoveredIngredient.isEmpty()) { + return Optional.empty(); + } + + return findBookmarkByTypedIngredient(overlay, hoveredIngredient.get()); + } + + private static Optional getElementFromClickable(Object clickable) { + if (clickable == null) { + return Optional.empty(); + } + try { + Object element = clickable.getClass().getMethod("getElement").invoke(clickable); + return Optional.ofNullable(element); + } catch (Throwable ignored) { + return Optional.empty(); + } + } + + private static Optional getBookmarkFromElement(Object element) { + if (element == null) { + return Optional.empty(); + } + try { + Object bookmarkOpt = element.getClass().getMethod("getBookmark").invoke(element); + if (bookmarkOpt instanceof Optional optional && optional.isPresent()) { + return Optional.ofNullable(optional.get()); + } + } catch (Throwable ignored) { + } + return Optional.empty(); + } + + private static Optional getTypedIngredientFromElement(Object element) { + if (element == null) { + return Optional.empty(); + } + try { + Object typed = element.getClass().getMethod("getTypedIngredient").invoke(element); + return Optional.ofNullable(typed); + } catch (Throwable ignored) { + return Optional.empty(); + } + } + + private static Optional getTypedIngredientFromBookmark(Object bookmark) { + if (bookmark == null) { + return Optional.empty(); + } + try { + Object typed = bookmark.getClass().getMethod("getDisplayIngredient").invoke(bookmark); + if (typed != null) { + return Optional.of(typed); + } + } catch (Throwable ignored) { + } + try { + Object typed = bookmark.getClass().getMethod("getIngredient").invoke(bookmark); + if (typed != null) { + return Optional.of(typed); + } + } catch (Throwable ignored) { + } + try { + Object element = bookmark.getClass().getMethod("getElement").invoke(bookmark); + return getTypedIngredientFromElement(element); + } catch (Throwable ignored) { + return Optional.empty(); + } + } + + private static Optional findBookmarkByTypedIngredient(Object overlay, Object hoveredIngredient) { + if (overlay == null || hoveredIngredient == null) { + return Optional.empty(); + } + Object firstMatch = null; + for (Object bookmark : getBookmarksFromOverlay(overlay)) { + Optional typedIngredient = getTypedIngredientFromBookmark(bookmark); + if (typedIngredient.isPresent() && hoveredIngredient.equals(typedIngredient.get())) { + if (isRecipeBookmark(bookmark)) { + return Optional.of(bookmark); + } + if (firstMatch == null) { + firstMatch = bookmark; + } + } + } + return Optional.ofNullable(firstMatch); + } + + private static List getBookmarksFromOverlay(Object overlay) { + if (overlay == null) { + return List.of(); + } + + List bookmarks = new ArrayList<>(); + Object bookmarkList = readFieldValue(overlay, "bookmarkList"); + Object bookmarkValues = readFieldValue(bookmarkList, "bookmarksList"); + addBookmarks(bookmarks, bookmarkValues); + + Object lookupHistoryOverlay = readFieldValue(overlay, "lookupHistoryOverlay"); + Object lookupHistory = readFieldValue(lookupHistoryOverlay, "lookupHistory"); + Object historyValues = readFieldValue(lookupHistory, "elements"); + addBookmarks(bookmarks, historyValues); + return bookmarks; + } + + private static void addBookmarks(List target, Object source) { + if (!(source instanceof List list)) { + return; + } + for (Object value : list) { + if (value != null) { + target.add(value); + } + } + } + + private static Object readFieldValue(Object owner, String fieldName) { + if (owner == null) { + return null; + } + try { + Field field = owner.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(owner); + } catch (Throwable ignored) { + return null; + } + } + + private static boolean isRecipeBookmark(Object bookmark) { + if (bookmark == null) { + return false; + } + if ("RecipeBookmark".equals(bookmark.getClass().getSimpleName())) { + return true; + } + try { + bookmark.getClass().getMethod("getRecipeCategory"); + bookmark.getClass().getMethod("getRecipe"); + return true; + } catch (Throwable ignored) { + return false; + } } }