适配JEI新版的历史记录

This commit is contained in:
GaLi 2026-03-25 16:04:42 +08:00
parent 8d6dc8ef9c
commit 7d621af169
4 changed files with 274 additions and 56 deletions

View File

@ -99,7 +99,7 @@ dependencies {
modRuntimeOnly "curse.maven:advancedae-1084104:6939473" modRuntimeOnly "curse.maven:advancedae-1084104:6939473"
modCompileOnly "mezz.jei:jei-${minecraft_version}-forge:${jei_version}" 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 "mezz.jei:jei-${minecraft_version}-forge:${jei_version}"
modImplementation "curse.maven:jade-324717:${jade_version}" modImplementation "curse.maven:jade-324717:${jade_version}"

View File

@ -16,7 +16,7 @@ glodium_version=5006780
ae2_version=15.4.5 ae2_version=15.4.5
guideme_version=20.1.7 guideme_version=20.1.7
wireless_terminals_version=5162352 wireless_terminals_version=5162352
jei_version=15.19.5.99 jei_version=15.20.0.129
applied_flux_version=5329825 applied_flux_version=5329825
mega_cells_version=5320730 mega_cells_version=5320730
jade_version=4768593 jade_version=4768593

View File

@ -19,6 +19,7 @@ import org.spongepowered.asm.mixin.Pseudo;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -56,6 +57,49 @@ public final class JeiBookmarkBridge {
return Collections.emptyList(); return Collections.emptyList();
} }
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse() {
return getIngredientUnderMouse(MouseUtil.getX(), MouseUtil.getY());
}
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse(double mouseX, double mouseY) {
IJeiRuntime rt = getRuntime();
if (rt == null) {
return Optional.empty();
}
Optional<ITypedIngredient<?>> 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<Object> bookmark = getBookmarkUnderMouse(bookmarkOverlay, mouseX, mouseY);
if (bookmark.isPresent()) {
return getTypedIngredientFromBookmark(bookmark.get());
}
return Optional.empty();
}
public static void addBookmark(ItemStack stack) { public static void addBookmark(ItemStack stack) {
IJeiRuntime rt = getRuntime(); IJeiRuntime rt = getRuntime();
if (rt == null) return; if (rt == null) return;
@ -167,64 +211,21 @@ public final class JeiBookmarkBridge {
* @return 配方书签对象RecipeBookmark<?, ?>如果不是配方书签则返回空 * @return 配方书签对象RecipeBookmark<?, ?>如果不是配方书签则返回空
*/ */
public static Optional<?> getRecipeBookmarkUnderMouse() { public static Optional<?> getRecipeBookmarkUnderMouse() {
IJeiRuntime rt = getRuntime(); Optional<?> bookmark = getBookmarkUnderMouse();
if (rt == null) return Optional.empty(); if (bookmark.isPresent() && isRecipeBookmark(bookmark.get())) {
return bookmark;
IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay();
if (!(bookmarkOverlay instanceof BookmarkOverlayAccessor accessor)) {
return Optional.empty();
} }
// 优先路径直接从鼠标下 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<ITypedIngredient<?>> 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(); 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 书签移除物品 * JEI 书签移除物品
*/ */
@ -243,4 +244,190 @@ public final class JeiBookmarkBridge {
}); });
} }
} }
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<ITypedIngredient<?>> getTypedIngredientFromClickableSource(Object owner, double mouseX, double mouseY) {
return getClickableIngredientUnderMouse(owner, mouseX, mouseY)
.flatMap(JeiBookmarkBridge::getElementFromClickable)
.flatMap(JeiBookmarkBridge::getTypedIngredientFromElement);
}
private static Optional<Object> getBookmarkUnderMouse(IBookmarkOverlay overlay, double mouseX, double mouseY) {
if (overlay == null) {
return Optional.empty();
}
Optional<Object> bookmark = getClickableIngredientUnderMouse(overlay, mouseX, mouseY)
.flatMap(JeiBookmarkBridge::getElementFromClickable)
.flatMap(JeiBookmarkBridge::getBookmarkFromElement);
if (bookmark.isPresent()) {
return bookmark;
}
Optional<ITypedIngredient<?>> 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<Object> 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<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<ITypedIngredient<?>> 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<ITypedIngredient<?>> 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<Object> findBookmarkByTypedIngredient(IBookmarkOverlay overlay, ITypedIngredient<?> hoveredIngredient) {
if (overlay == null || hoveredIngredient == null) {
return Optional.empty();
}
Object firstMatch = null;
for (Object bookmark : getBookmarksFromOverlay(overlay)) {
Optional<ITypedIngredient<?>> 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(IBookmarkOverlay 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;
}
}
} }

View File

@ -35,6 +35,14 @@ public final class JeiRuntimeProxy {
} }
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse() { public static Optional<ITypedIngredient<?>> 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; IJeiRuntime rt = RUNTIME;
if (rt == null) return Optional.empty(); if (rt == null) return Optional.empty();
@ -55,6 +63,17 @@ public final class JeiRuntimeProxy {
* JEI 配方界面区域内基于屏幕坐标查询鼠标下的配料优先物品其次流体 * JEI 配方界面区域内基于屏幕坐标查询鼠标下的配料优先物品其次流体
*/ */
public static Optional<ITypedIngredient<?>> getIngredientUnderMouse(double mouseX, double mouseY) { public static Optional<ITypedIngredient<?>> 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<ITypedIngredient<?>> result = (Optional<ITypedIngredient<?>>) method.invoke(null, mouseX, mouseY);
if (result != null && result.isPresent()) {
return result;
}
} catch (Throwable ignored) {
}
IJeiRuntime rt = RUNTIME; IJeiRuntime rt = RUNTIME;
if (rt == null || rt.getRecipesGui() == null) return Optional.empty(); 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. // Note: helper methods moved to bridge to avoid referencing JEI GUI at class load time.
/** /**