适配JEI新版的历史记录

This commit is contained in:
GaLi 2026-03-25 15:03:00 +08:00
parent 439eea7bfe
commit 9d73964196
2 changed files with 351 additions and 122 deletions

View File

@ -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
}

View File

@ -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<Object> ingredient = getRuntimeComponent(rt, "getIngredientListOverlay")
.flatMap(overlay -> getTypedIngredientFromClickableSource(overlay, mouseX, mouseY));
if (ingredient.isPresent()) {
return ingredient;
}
Optional<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> getTypedIngredientFromClickableSource(Object owner, double mouseX, double mouseY) {
return getClickableIngredientUnderMouse(owner, mouseX, mouseY)
.flatMap(JeiRuntimeProxy::getElementFromClickable)
.flatMap(JeiRuntimeProxy::getTypedIngredientFromElement);
}
private static Optional<Object> getBookmarkUnderMouse(Object overlay, double mouseX, double mouseY) {
Optional<Object> bookmark = getClickableIngredientUnderMouse(overlay, mouseX, mouseY)
.flatMap(JeiRuntimeProxy::getElementFromClickable)
.flatMap(JeiRuntimeProxy::getBookmarkFromElement);
if (bookmark.isPresent()) {
return bookmark;
}
Optional<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> findBookmarkByTypedIngredient(Object overlay, Object hoveredIngredient) {
if (overlay == null || hoveredIngredient == null) {
return Optional.empty();
}
Object firstMatch = null;
for (Object bookmark : getBookmarksFromOverlay(overlay)) {
Optional<Object> 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<Object> getBookmarksFromOverlay(Object overlay) {
if (overlay == null) {
return List.of();
}
List<Object> 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<Object> 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;
}
}
}