diff --git a/build.gradle b/build.gradle index 20f01cf..5e8862f 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ dependencies { modRuntimeOnly "curse.maven:advancedae-1084104:6939473" modCompileOnly "mezz.jei:jei-${minecraft_version}-forge:${jei_version}" - modRuntimeOnly "mezz.jei:jei-${minecraft_version}-forge:15.20.0.112" + modRuntimeOnly "mezz.jei:jei-${minecraft_version}-forge:15.20.0.129" modImplementation "mezz.jei:jei-${minecraft_version}-forge:${jei_version}" modImplementation "curse.maven:jade-324717:${jade_version}" diff --git a/gradle.properties b/gradle.properties index aa6164b..f9c6a31 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ glodium_version=5006780 ae2_version=15.4.5 guideme_version=20.1.7 wireless_terminals_version=5162352 -jei_version=15.19.5.99 +jei_version=15.20.0.129 applied_flux_version=5329825 mega_cells_version=5320730 jade_version=4768593 diff --git a/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java b/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java index 1f24f2c..61bc9f5 100644 --- a/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java +++ b/src/main/java/com/extendedae_plus/integration/jei/JeiBookmarkBridge.java @@ -19,6 +19,7 @@ import org.spongepowered.asm.mixin.Pseudo; 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; @@ -56,6 +57,49 @@ public final class JeiBookmarkBridge { return Collections.emptyList(); } + public static Optional> getIngredientUnderMouse() { + return getIngredientUnderMouse(MouseUtil.getX(), MouseUtil.getY()); + } + + public static Optional> getIngredientUnderMouse(double mouseX, double mouseY) { + IJeiRuntime rt = getRuntime(); + if (rt == null) { + return Optional.empty(); + } + + Optional> ingredient = getTypedIngredientFromClickableSource(rt.getIngredientListOverlay(), mouseX, mouseY); + if (ingredient.isPresent()) { + return ingredient; + } + + IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); + ingredient = getTypedIngredientFromClickableSource(bookmarkOverlay, mouseX, mouseY); + if (ingredient.isPresent()) { + return ingredient; + } + + if (rt.getIngredientListOverlay() != null) { + var hovered = rt.getIngredientListOverlay().getIngredientUnderMouse(); + if (hovered.isPresent()) { + return hovered.map(i -> (ITypedIngredient) i); + } + } + + if (bookmarkOverlay != null) { + var hovered = bookmarkOverlay.getIngredientUnderMouse(); + if (hovered.isPresent()) { + return hovered.map(i -> (ITypedIngredient) i); + } + } + + Optional bookmark = getBookmarkUnderMouse(bookmarkOverlay, mouseX, mouseY); + if (bookmark.isPresent()) { + return getTypedIngredientFromBookmark(bookmark.get()); + } + + return Optional.empty(); + } + public static void addBookmark(ItemStack stack) { IJeiRuntime rt = getRuntime(); if (rt == null) return; @@ -167,64 +211,21 @@ public final class JeiBookmarkBridge { * @return 配方书签对象(RecipeBookmark),如果不是配方书签则返回空 */ public static Optional getRecipeBookmarkUnderMouse() { - IJeiRuntime rt = getRuntime(); - if (rt == null) return Optional.empty(); - - IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); - if (!(bookmarkOverlay instanceof BookmarkOverlayAccessor accessor)) { - return Optional.empty(); + Optional bookmark = getBookmarkUnderMouse(); + if (bookmark.isPresent() && isRecipeBookmark(bookmark.get())) { + return bookmark; } - - // 优先路径:直接从鼠标下 clickable 元素提取 bookmark(避免 typedIngredient equals 误判) - try { - var overlayObj = (Object) bookmarkOverlay; - var streamObj = overlayObj.getClass() - .getMethod("getIngredientUnderMouse", double.class, double.class) - .invoke(overlayObj, MouseUtil.getX(), MouseUtil.getY()); - if (streamObj instanceof java.util.stream.Stream stream) { - Object clickable = stream.findFirst().orElse(null); - if (clickable != null) { - Object element = clickable.getClass().getMethod("getElement").invoke(clickable); - 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) { - } - - // 获取鼠标下的元素 - Optional> ingredientOpt = bookmarkOverlay.getIngredientUnderMouse(); - if (ingredientOpt.isEmpty()) { - return Optional.empty(); - } - - // 遍历书签列表,查找匹配的配方书签 - BookmarkList bookmarkList = accessor.eap$getBookmarkList(); - for (IElement element : bookmarkList.getElements()) { - // 检查元素的 TypedIngredient 是否匹配 - if (element.getTypedIngredient().equals(ingredientOpt.get())) { - // 检查是否有关联的书签 - Optional bookmarkOpt = element.getBookmark(); - if (bookmarkOpt.isPresent()) { - Object bookmark = bookmarkOpt.get(); - // 判断是否为 RecipeBookmark(而非 IngredientBookmark) - if (bookmark.getClass().getSimpleName().equals("RecipeBookmark")) { - return Optional.of(bookmark); - } - } - } - } - return Optional.empty(); } + public static Optional getBookmarkUnderMouse() { + IJeiRuntime rt = getRuntime(); + if (rt == null) { + return Optional.empty(); + } + return getBookmarkUnderMouse(rt.getBookmarkOverlay(), MouseUtil.getX(), MouseUtil.getY()); + } + /** * 从 JEI 书签移除物品 */ @@ -243,4 +244,190 @@ public final class JeiBookmarkBridge { }); } } + + 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(JeiBookmarkBridge::getElementFromClickable) + .flatMap(JeiBookmarkBridge::getTypedIngredientFromElement); + } + + private static Optional getBookmarkUnderMouse(IBookmarkOverlay overlay, double mouseX, double mouseY) { + if (overlay == null) { + return Optional.empty(); + } + + Optional bookmark = getClickableIngredientUnderMouse(overlay, mouseX, mouseY) + .flatMap(JeiBookmarkBridge::getElementFromClickable) + .flatMap(JeiBookmarkBridge::getBookmarkFromElement); + if (bookmark.isPresent()) { + return bookmark; + } + + Optional> hoveredIngredient = getTypedIngredientFromClickableSource(overlay, mouseX, mouseY); + if (hoveredIngredient.isEmpty()) { + hoveredIngredient = 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 { + return Optional.ofNullable(clickable.getClass().getMethod("getElement").invoke(clickable)); + } 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); + if (typed instanceof ITypedIngredient typedIngredient) { + return Optional.of(typedIngredient); + } + } 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 instanceof ITypedIngredient typedIngredient) { + return Optional.of(typedIngredient); + } + } catch (Throwable ignored) { + } + try { + Object typed = bookmark.getClass().getMethod("getIngredient").invoke(bookmark); + if (typed instanceof ITypedIngredient typedIngredient) { + return Optional.of(typedIngredient); + } + } 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(IBookmarkOverlay overlay, ITypedIngredient 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(IBookmarkOverlay 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; + } + } } 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 089ea9b..e18a2ea 100644 --- a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java +++ b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java @@ -35,6 +35,14 @@ public final class JeiRuntimeProxy { } public static Optional> getIngredientUnderMouse() { + try { + Class mouseUtil = Class.forName("mezz.jei.gui.input.MouseUtil"); + double mouseX = ((Number) mouseUtil.getMethod("getX").invoke(null)).doubleValue(); + double mouseY = ((Number) mouseUtil.getMethod("getY").invoke(null)).doubleValue(); + return getIngredientUnderMouse(mouseX, mouseY); + } catch (Throwable ignored) { + } + IJeiRuntime rt = RUNTIME; if (rt == null) return Optional.empty(); @@ -55,6 +63,17 @@ public final class JeiRuntimeProxy { * 在 JEI 配方界面区域内,基于屏幕坐标查询鼠标下的配料(优先物品,其次流体)。 */ public static Optional> getIngredientUnderMouse(double mouseX, double mouseY) { + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var method = bridge.getMethod("getIngredientUnderMouse", double.class, double.class); + @SuppressWarnings("unchecked") + Optional> result = (Optional>) method.invoke(null, mouseX, mouseY); + if (result != null && result.isPresent()) { + return result; + } + } catch (Throwable ignored) { + } + IJeiRuntime rt = RUNTIME; if (rt == null || rt.getRecipesGui() == null) return Optional.empty(); @@ -184,6 +203,18 @@ public final class JeiRuntimeProxy { } } + public static Optional getBookmarkUnderMouse() { + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("getBookmarkUnderMouse"); + @SuppressWarnings("unchecked") + Optional result = (Optional) m.invoke(null); + return result == null ? Optional.empty() : result; + } catch (Throwable ignored) { + return Optional.empty(); + } + } + // Note: helper methods moved to bridge to avoid referencing JEI GUI at class load time. /**