diff --git a/build.gradle b/build.gradle index 54886d7..5c39e70 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ dependencies { compileOnly "curse.maven:jade-324717:5427817" compileOnly "me.shedaniel:RoughlyEnoughItems-neoforge:16.0.729" compileOnly "curse.maven:applied-mekanistics-574300:5978711" - compileOnly "mekanism:Mekanism:1.21.1-10.7.15.80:api" + compileOnly "mekanism:Mekanism:1.21.1-10.7.15.81:api" compileOnly "dev.latvian.mods:kubejs-neoforge:2101.7.1-build.188" compileOnly "dev.latvian.mods:rhino:2101.2.5-build.54" compileOnly "curse.maven:framedblocks-441647:6127891" diff --git a/gradle.properties b/gradle.properties index e8fede5..5d07806 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ loader_version_range=[1,) mod_id=extendedae_plus mod_name=ExtendedAE-Plus mod_license=LGPL-3.0-or-later -mod_version=1.0.2 +mod_version=1.0.2jei mod_group_id=com.extendedae_plus mod_authors=GaLicn, XianYuFish001 mod_description= diff --git a/src/main/java/com/extendedae_plus/client/InputEvents.java b/src/main/java/com/extendedae_plus/client/InputEvents.java index c3ca7e2..b6ade6c 100644 --- a/src/main/java/com/extendedae_plus/client/InputEvents.java +++ b/src/main/java/com/extendedae_plus/client/InputEvents.java @@ -3,16 +3,14 @@ package com.extendedae_plus.client; import appeng.api.stacks.GenericStack; import appeng.client.gui.me.common.MEStorageScreen; import appeng.core.AEConfig; -import appeng.integration.modules.emi.EmiStackHelper; import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.integration.RecipeViewer.RecipeViewerHelper; import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor; import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; import com.extendedae_plus.network.C2SPacketTargetKeyTriggered; import com.extendedae_plus.network.OpenCraftFromJeiC2SPacket; import com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; -import dev.emi.emi.api.EmiApi; -import dev.emi.emi.api.stack.EmiStack; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.neoforged.api.distmarker.Dist; @@ -34,14 +32,10 @@ public final class InputEvents { if (event.getAction() != GLFW.GLFW_PRESS) return; if (Minecraft.getInstance().screen == null) return; // 优先处理:Shift + 左键(拉取或下单) - if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) { - // 🤓哎wc这Jemi兼容层怎么这么好用啊( - // 😡我们要完全去jei化, 不准用 - // 😭 - // Optional> hovered = new JemiBookmarkOverlay().getIngredientUnderMouse(); - List stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks(); - if (stacks.isEmpty()) return; - GenericStack stack = EmiStackHelper.toGenericStack(stacks.getFirst()); + if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown() && + !RecipeViewerHelper.isCheatMode()) { + List stacks = RecipeViewerHelper.getHoveredStacks(); + GenericStack stack = stacks.isEmpty() ? null : stacks.getFirst(); if (stack == null) return; PacketDistributor.sendToServer(new PullFromJeiOrCraftC2SPacket(stack)); } @@ -49,10 +43,8 @@ public final class InputEvents { // 中键:打开 AE 下单界面(保持原有功能) if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) { // 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取 - List stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks(); - if (stacks.isEmpty()) return; - - GenericStack stack = EmiStackHelper.toGenericStack(stacks.getFirst()); + List stacks = RecipeViewerHelper.getHoveredStacks(); + GenericStack stack = stacks.isEmpty() ? null : stacks.getFirst(); if (stack == null) return; // 发送到服务端,让其验证并打开 CraftAmountMenu @@ -68,17 +60,14 @@ public final class InputEvents { if (event.getKeyCode() == GLFW.GLFW_KEY_F) { // 仅当鼠标确实悬停在 JEI 配料上时触发 // 大概会在一格有多个(?)stack的时候出bug, 但是真的会有那种时候吗? - List stack = EmiApi.getHoveredStack(false).getStack().getEmiStacks(); - if (stack.isEmpty()) return; - String name = stack.getFirst().getName().getString(); - if (name.isEmpty()) return; + String name = RecipeViewerHelper.getHoveredStacks().getFirst().what().getDisplayName().getString(); // 写入 AE2 终端的搜索框 var screen = Minecraft.getInstance().screen; if (screen instanceof MEStorageScreen me) { try { // 如果用EMI搜索框 - if (AEConfig.instance().isUseExternalSearch()) EmiApi.setSearchText(name); + if (AEConfig.instance().isUseExternalSearch()) RecipeViewerHelper.setSearchText(name); else { MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me; acc.eap$getSearchField().setValue(name); @@ -89,7 +78,7 @@ public final class InputEvents { } } else if (screen instanceof GuiExPatternTerminal gpt) { try { - if (AEConfig.instance().isUseExternalSearch()) EmiApi.setSearchText(name); + if (AEConfig.instance().isUseExternalSearch()) RecipeViewerHelper.setSearchText(name); else { GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt; acc.getSearchField().setValue(name); diff --git a/src/main/java/com/extendedae_plus/client/gui/PageLayoutContext.java b/src/main/java/com/extendedae_plus/client/gui/PageLayoutContext.java deleted file mode 100644 index 7cf88f0..0000000 --- a/src/main/java/com/extendedae_plus/client/gui/PageLayoutContext.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.extendedae_plus.client.gui; - -public final class PageLayoutContext { - private static final ThreadLocal ACTIVE = ThreadLocal.withInitial(() -> false); - private static final ThreadLocal CURRENT_PAGE = ThreadLocal.withInitial(() -> 0); - - private PageLayoutContext() {} - - public static void enable(int page) { - ACTIVE.set(true); - CURRENT_PAGE.set(page); - } - - public static void disable() { - ACTIVE.set(false); - } - - public static boolean isActive() { - Boolean b = ACTIVE.get(); - return b != null && b; - } - - public static int getCurrentPage() { - Integer i = CURRENT_PAGE.get(); - return i != null ? i : 0; - } - - public static void withPage(int page, Runnable action) { - enable(page); - try { - action.run(); - } finally { - disable(); - } - } -} diff --git a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java similarity index 99% rename from src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java rename to src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java index d342a8c..6171121 100644 --- a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java +++ b/src/main/java/com/extendedae_plus/client/screen/ProviderSelectScreen.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.client.ui; +package com.extendedae_plus.client.screen; import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket; import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/EmptyHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/EmptyHelper.java new file mode 100644 index 0000000..17a7141 --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/EmptyHelper.java @@ -0,0 +1,31 @@ +package com.extendedae_plus.integration.RecipeViewer; + +import appeng.api.stacks.GenericStack; + +import java.util.List; + +public class EmptyHelper implements IRecipeViewerHelper{ + @Override + public List getHoveredStacks(double mouseX, double mouseY) { + return List.of(); + } + + @Override + public List getHoveredStacks() { + return List.of(); + } + + @Override + public List getFavorites() { + return List.of(); + } + + @Override + public void addFavorite(GenericStack stack) { + } + + @Override + public void setSearch(String text) { + + } +} diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/IRecipeViewerHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/IRecipeViewerHelper.java new file mode 100644 index 0000000..63ff5cc --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/IRecipeViewerHelper.java @@ -0,0 +1,21 @@ +package com.extendedae_plus.integration.RecipeViewer; + +import appeng.api.stacks.GenericStack; + +import java.util.List; + +public interface IRecipeViewerHelper { + List getHoveredStacks(double mouseX, double mouseY); + + List getHoveredStacks(); + + List getFavorites(); + + default boolean isCheatMode() { + return false; + } + + void addFavorite(GenericStack stack); + + void setSearch(String text); +} diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/RecipeViewerHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/RecipeViewerHelper.java new file mode 100644 index 0000000..cd25a6d --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/RecipeViewerHelper.java @@ -0,0 +1,44 @@ +package com.extendedae_plus.integration.RecipeViewer; + +import appeng.api.stacks.GenericStack; +import com.extendedae_plus.integration.RecipeViewer.emi.EMIHelper; +import com.extendedae_plus.integration.RecipeViewer.jei.JeiHelper; +import net.neoforged.fml.ModList; + +import java.util.List; +import java.util.Optional; + +public class RecipeViewerHelper { + private static IRecipeViewerHelper activeViewer; + + public static void init() { + if (ModList.get().isLoaded("emi")) activeViewer = new EMIHelper(); + else if (ModList.get().isLoaded("jei")) activeViewer = new JeiHelper(); + else activeViewer = new EmptyHelper(); + } + + public static Optional getViewer() { + if (activeViewer == null) init(); + return Optional.ofNullable(activeViewer); + } + + public static List getHoveredStacks() { + return getViewer().map(IRecipeViewerHelper::getHoveredStacks).orElse(List.of()); + } + + public static List getFavorites() { + return getViewer().map(IRecipeViewerHelper::getFavorites).orElse(List.of()); + } + + public static boolean isCheatMode() { + return getViewer().map(IRecipeViewerHelper::isCheatMode).orElse(false); + } + + public static void addFavorite(GenericStack stack) { + getViewer().ifPresent(viewer -> viewer.addFavorite(stack)); + } + + public static void setSearchText(String text) { + getViewer().ifPresent(viewer -> viewer.setSearch(text)); + } +} diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/EMIHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/EMIHelper.java new file mode 100644 index 0000000..8c9bdc0 --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/EMIHelper.java @@ -0,0 +1,90 @@ +package com.extendedae_plus.integration.RecipeViewer.emi; + +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.integration.modules.emi.EmiStackHelper; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.integration.RecipeViewer.IRecipeViewerHelper; +import dev.emi.emi.api.EmiApi; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.runtime.EmiFavorite; +import dev.emi.emi.runtime.EmiFavorites; +import net.neoforged.fml.ModList; + +import java.lang.reflect.Method; +import java.util.List; + +public class EMIHelper implements IRecipeViewerHelper { + private final Object mekHelper = getMekHelper(); + private final boolean mekChecked = + ModList.get().isLoaded("mekanism") || + ModList.get().isLoaded("appmek"); + + @Override + public List getHoveredStacks(double mouseX, double mouseY) { + return EmiApi.getHoveredStack((int) mouseX, (int) mouseY, false).getStack().getEmiStacks() + .stream().map(EmiStackHelper::toGenericStack).toList(); + } + + @Override + public List getHoveredStacks() { + return EmiApi.getHoveredStack(false).getStack().getEmiStacks() + .stream().map(EmiStackHelper::toGenericStack).toList(); + } + + @Override + public List getFavorites() { + return EmiFavorites.favorites.stream() + .map(EmiFavorite::getEmiStacks) + .map(List::getFirst) + .map(EmiStackHelper::toGenericStack) + .toList(); + } + + @Override + public void addFavorite(GenericStack stack) { + AEKey key = stack.what(); + if (key instanceof AEItemKey itemKey) + EmiFavorites.addFavorite(EmiStack.of(itemKey.toStack((int) stack.amount()))); + else if (key instanceof AEFluidKey fluidKey) + EmiFavorites.addFavorite(EmiStack.of(fluidKey.getFluid(), stack.amount())); + else if (mekChecked && mekHelper != null) { + try { + Class keyClass = key.getClass(); + if (keyClass.getName().contains("MekanismKey")) { + Method getChemicalStackMethod = keyClass.getMethod("withAmount", long.class); + Object chemicalStack = getChemicalStackMethod.invoke(key, stack.amount()); + + Method createEmiStack = mekHelper.getClass().getMethod("createEmiStack", + Class.forName("mekanism.api.chemical.ChemicalStack")); + EmiStack mekEmiStack = (EmiStack) createEmiStack.invoke(mekHelper, chemicalStack); + if (mekEmiStack == null) return; + EmiFavorites.addFavorite(mekEmiStack); + } + } catch (Exception ignored) {} + } + } + + @Override + public void setSearch(String text) { + EmiApi.setSearchText(text); + } + + private Object getMekHelper() { + try { + Class mekanismAPIClass = Class.forName("mekanism.api.MekanismAPI"); + Method getServiceMethod = mekanismAPIClass.getMethod("getService", Class.class); + + Class mekanismAccessClass = Class.forName("mekanism.api.IMekanismAccess"); + Object mekanismAccess = getServiceMethod.invoke(null, mekanismAccessClass); + + Method emiHelperMethod = mekanismAccessClass.getMethod("emiHelper"); + return emiHelperMethod.invoke(mekanismAccess); + } catch (Exception e) { + ExtendedAEPlus.LOGGER.error("Failed to get Mekanism EMI Helper: {}", String.valueOf(e)); + return null; + } + } +} diff --git a/src/main/java/com/extendedae_plus/integration/emi/HandlerBoMRecipes.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/HandlerBoMRecipes.java similarity index 97% rename from src/main/java/com/extendedae_plus/integration/emi/HandlerBoMRecipes.java rename to src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/HandlerBoMRecipes.java index ae844b4..707933c 100644 --- a/src/main/java/com/extendedae_plus/integration/emi/HandlerBoMRecipes.java +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/HandlerBoMRecipes.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.integration.emi; +package com.extendedae_plus.integration.RecipeViewer.emi; import appeng.api.stacks.GenericStack; import appeng.integration.modules.emi.EmiStackHelper; diff --git a/src/main/java/com/extendedae_plus/integration/emi/PatternFillingHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/PatternFillingHelper.java similarity index 98% rename from src/main/java/com/extendedae_plus/integration/emi/PatternFillingHelper.java rename to src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/PatternFillingHelper.java index 12fc5f8..c7329d5 100644 --- a/src/main/java/com/extendedae_plus/integration/emi/PatternFillingHelper.java +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/emi/PatternFillingHelper.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.integration.emi; +package com.extendedae_plus.integration.RecipeViewer.emi; import appeng.api.stacks.AEItemKey; import appeng.api.stacks.GenericStack; diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiHelper.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiHelper.java new file mode 100644 index 0000000..1720249 --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiHelper.java @@ -0,0 +1,64 @@ +package com.extendedae_plus.integration.RecipeViewer.jei; + +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import com.extendedae_plus.integration.RecipeViewer.IRecipeViewerHelper; +import mezz.jei.api.ingredients.ITypedIngredient; +import net.neoforged.fml.ModList; +import tamaized.ae2jeiintegration.integration.modules.jei.GenericEntryStackHelper; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +public class JeiHelper implements IRecipeViewerHelper { + @Override + public List getHoveredStacks(double mouseX, double mouseY) { + return getHoveredStacks(); + } + + @Override + public List getHoveredStacks() { + ITypedIngredient hovered = JeiRuntimeProxy.getIngredientUnderMouse().orElse(null); + if (hovered != null) + return Collections.singletonList(GenericEntryStackHelper.ingredientToStack(hovered)); + else return null; + } + + @Override + public List getFavorites() { + return JeiRuntimeProxy.getBookmarkList().stream() + .map(GenericEntryStackHelper::ingredientToStack).toList(); + } + + @Override + public boolean isCheatMode() { + return JeiRuntimeProxy.isJeiCheatModeEnabled(); + } + + @Override + public void addFavorite(GenericStack stack) { + AEKey key = stack.what(); + if (key instanceof AEItemKey itemKey) + JeiRuntimeProxy.addBookmark(itemKey.toStack()); + else if (key instanceof AEFluidKey fluidKey) + JeiRuntimeProxy.addBookmark(fluidKey.toStack(1000)); + else if (ModList.get().isLoaded("mekanism") || ModList.get().isLoaded("appmek")) { + try { + Class keyClass = key.getClass(); + if (keyClass.getName().contains("MekanismKey")) { + Method getChemicalStackMethod = keyClass.getMethod("getStack"); + Object chemicalStack = getChemicalStackMethod.invoke(key); + JeiRuntimeProxy.addBookmark(chemicalStack); + } + } catch (Exception ignored) {} + } + } + + @Override + public void setSearch(String text) { + JeiRuntimeProxy.setIngredientFilterText(text); + } +} diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiRuntimeProxy.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiRuntimeProxy.java new file mode 100644 index 0000000..38505ce --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiRuntimeProxy.java @@ -0,0 +1,196 @@ +package com.extendedae_plus.integration.RecipeViewer.jei; + +import com.extendedae_plus.mixin.recipeViewer.jei.accessor.AccessorBookmarkOverlay; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.ingredients.IIngredientType; +import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.neoforge.NeoForgeTypes; +import mezz.jei.api.runtime.IBookmarkOverlay; +import mezz.jei.api.runtime.IIngredientListOverlay; +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.neoforged.fml.ModList; +import net.neoforged.neoforge.fluids.FluidStack; + +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 Runtime。 + */ +public final class JeiRuntimeProxy { + private static volatile IJeiRuntime RUNTIME; + + private JeiRuntimeProxy() {} + + static void setRuntime(IJeiRuntime runtime) { + RUNTIME = runtime; + } + + @Nullable + public static IJeiRuntime get() { + return RUNTIME; + } + + public static Optional> getIngredientUnderMouse() { + IJeiRuntime rt = RUNTIME; + if (rt == null) return Optional.empty(); + + IIngredientListOverlay list = rt.getIngredientListOverlay(); + if (list != null) { + var ing = list.getIngredientUnderMouse(); + if (ing.isPresent()) return ing; + } + IBookmarkOverlay bm = rt.getBookmarkOverlay(); + if (bm != null) { + var ing = bm.getIngredientUnderMouse(); + if (ing.isPresent()) return ing; + } + return Optional.empty(); + } + + /** + * 检测 JEI 是否开启了作弊模式(给物品)。 + * 使用 JEI 内部开关,若 JEI 未初始化或异常则返回 false。 + */ + public static boolean isJeiCheatModeEnabled() { + try { + // 使用完全限定名以避免在源码缺失时的编译依赖问题 + return mezz.jei.common.Internal.getClientToggleState().isCheatItemsEnabled(); + } catch (Throwable t) { + return false; + } + } + + /** + * 将文本写入 JEI 的搜索过滤框。 + * 若 JEI runtime 不可用则静默返回。 + */ + public static void setIngredientFilterText(String text) { + IJeiRuntime rt = RUNTIME; + if (rt == null) return; + try { + rt.getIngredientFilter().setFilterText(text == null ? "" : text); + } catch (Throwable ignored) { + // 兼容不同 JEI 版本或在启动阶段尚未就绪 + } + } + + /** + * 获取JEI书签列表 + */ + public static List> getBookmarkList() { + IJeiRuntime rt = RUNTIME; + if (rt == null) return Collections.emptyList(); + IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); + if (bookmarkOverlay instanceof AccessorBookmarkOverlay accessor) { + BookmarkList bookmarkList = accessor.eap$getBookmarkList(); + return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList(); + } + return Collections.emptyList(); + } + + /** + * 将物品添加到 JEI 书签 + */ + public static void addBookmark(ItemStack stack) { + IJeiRuntime rt = RUNTIME; + if (rt == null || stack == null || stack.isEmpty()) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof AccessorBookmarkOverlay accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + try { + var typedOpt = rt.getIngredientManager().createTypedIngredient(VanillaTypes.ITEM_STACK, stack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.add(bookmark); // add 内部会自动保存到配置 + }); + } catch (Throwable ignored) {} + } + } + + public static void addBookmark(FluidStack fluidStack) { + IJeiRuntime rt = RUNTIME; + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof AccessorBookmarkOverlay accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(NeoForgeTypes.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 = RUNTIME; + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof AccessorBookmarkOverlay 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.recipe_viewer.jei.MekanismJEI"; + Class jeiCls = Class.forName(mekanismJeiClass); + Field typeField = null; + if ("mekanism.api.chemical.ChemicalStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_CHEMICAL"); + } + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/ModJeiPlugin.java b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/ModJeiPlugin.java new file mode 100644 index 0000000..522d346 --- /dev/null +++ b/src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/ModJeiPlugin.java @@ -0,0 +1,23 @@ +package com.extendedae_plus.integration.RecipeViewer.jei; + +import com.extendedae_plus.ExtendedAEPlus; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.runtime.IJeiRuntime; +import net.minecraft.resources.ResourceLocation; + +@JeiPlugin +public class ModJeiPlugin implements IModPlugin { + private static final ResourceLocation UID = + ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "jei_plugin"); + + @Override + public ResourceLocation getPluginUid() { + return UID; + } + + @Override + public void onRuntimeAvailable(IJeiRuntime jeiRuntime) { + JeiRuntimeProxy.setRuntime(jeiRuntime); + } +} diff --git a/src/main/java/com/extendedae_plus/integration/RecipeViewer/不要问为什么这个文件夹首字母是大写的,因为小写就有bug b/src/main/java/com/extendedae_plus/integration/RecipeViewer/不要问为什么这个文件夹首字母是大写的,因为小写就有bug new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java b/src/main/java/com/extendedae_plus/mixin/ModelBakeryMixin.java similarity index 95% rename from src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java rename to src/main/java/com/extendedae_plus/mixin/ModelBakeryMixin.java index f3a0f8c..b30e468 100644 --- a/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ModelBakeryMixin.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.hooks; +package com.extendedae_plus.mixin; import com.extendedae_plus.hooks.BuiltInModelHooks; import net.minecraft.client.resources.model.ModelBakery; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java index 915188e..be0671e 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java @@ -1,12 +1,11 @@ package com.extendedae_plus.mixin.ae2.menu; +import appeng.api.stacks.GenericStack; import appeng.menu.me.crafting.CraftConfirmMenu; import appeng.menu.me.crafting.CraftingPlanSummary; import appeng.menu.me.crafting.CraftingPlanSummaryEntry; -import dev.emi.emi.api.stack.EmiIngredient; -import dev.emi.emi.runtime.EmiFavorites; +import com.extendedae_plus.integration.RecipeViewer.RecipeViewerHelper; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.item.crafting.Ingredient; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -45,12 +44,9 @@ public class CraftConfirmMenuGoBackMixin { for (CraftingPlanSummaryEntry entry : entries) { if (entry.getMissingAmount() > 0) { try { - var display = entry.getWhat().wrapForDisplayOrFilter(); - if (display != null && !display.isEmpty()) { - // wrapForDisplayOrFilter 返回 ItemStack -// JeiRuntimeProxy.addBookmark(display); - EmiFavorites.addFavorite(EmiIngredient.of(Ingredient.of(display))); - } + var display = entry.getWhat(); + if (display != null) + RecipeViewerHelper.addFavorite(new GenericStack(display, entry.getMissingAmount())); } catch (Throwable ignored) {} } } diff --git a/src/main/java/com/extendedae_plus/mixin/emi/MixinEncodingHelper.java b/src/main/java/com/extendedae_plus/mixin/recipeViewer/MixinEncodingHelper.java similarity index 65% rename from src/main/java/com/extendedae_plus/mixin/emi/MixinEncodingHelper.java rename to src/main/java/com/extendedae_plus/mixin/recipeViewer/MixinEncodingHelper.java index 1d14b9f..8183cd9 100644 --- a/src/main/java/com/extendedae_plus/mixin/emi/MixinEncodingHelper.java +++ b/src/main/java/com/extendedae_plus/mixin/recipeViewer/MixinEncodingHelper.java @@ -1,14 +1,10 @@ -package com.extendedae_plus.mixin.emi; +package com.extendedae_plus.mixin.recipeViewer; -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEKey; import appeng.integration.modules.itemlists.EncodingHelper; import appeng.menu.me.common.GridInventoryEntry; import appeng.menu.me.common.MEStorageMenu; -import dev.emi.emi.runtime.EmiFavorites; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.material.Fluid; +import com.extendedae_plus.integration.RecipeViewer.RecipeViewerHelper; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -25,12 +21,9 @@ public class MixinEncodingHelper { private static void epp$addJeiIngredientPriorities(MEStorageMenu menu, Comparator comparator, CallbackInfoReturnable> cir){ Map result = cir.getReturnValue(); AtomicInteger index = new AtomicInteger(Integer.MAX_VALUE); - EmiFavorites.favorites.forEach(favorite -> { - Object stack = favorite.getEmiStacks().getFirst().getKey(); - if (stack instanceof Item item) result.put(AEItemKey.of(item), index.getAndDecrement()); - else if (stack instanceof Fluid fluid) result.put(AEFluidKey.of(fluid), index.getAndDecrement()); + RecipeViewerHelper.getFavorites().forEach(favorite -> { + if (favorite != null) result.put(favorite.what(), index.getAndDecrement()); }); cir.setReturnValue(result); - } } diff --git a/src/main/java/com/extendedae_plus/mixin/emi/MixinBoMScreen.java b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinBoMScreen.java similarity index 96% rename from src/main/java/com/extendedae_plus/mixin/emi/MixinBoMScreen.java rename to src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinBoMScreen.java index a03bd0f..aa7c110 100644 --- a/src/main/java/com/extendedae_plus/mixin/emi/MixinBoMScreen.java +++ b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinBoMScreen.java @@ -1,9 +1,9 @@ -package com.extendedae_plus.mixin.emi; +package com.extendedae_plus.mixin.recipeViewer.emi; import appeng.integration.modules.emi.EmiStackHelper; import appeng.menu.me.items.PatternEncodingTermMenu; -import com.extendedae_plus.integration.emi.HandlerBoMRecipes; -import com.extendedae_plus.integration.emi.PatternFillingHelper; +import com.extendedae_plus.integration.RecipeViewer.emi.HandlerBoMRecipes; +import com.extendedae_plus.integration.RecipeViewer.emi.PatternFillingHelper; import com.extendedae_plus.network.RequestProvidersListC2SPacket; import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; import dev.emi.emi.api.stack.EmiStack; diff --git a/src/main/java/com/extendedae_plus/mixin/emi/MixinEncodePatternTransfer.java b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinEncodePatternTransfer.java similarity index 97% rename from src/main/java/com/extendedae_plus/mixin/emi/MixinEncodePatternTransfer.java rename to src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinEncodePatternTransfer.java index 0d23a17..ed0d868 100644 --- a/src/main/java/com/extendedae_plus/mixin/emi/MixinEncodePatternTransfer.java +++ b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinEncodePatternTransfer.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.emi; +package com.extendedae_plus.mixin.recipeViewer.emi; import appeng.integration.modules.emi.EmiEncodePatternHandler; import appeng.integration.modules.itemlists.EncodingHelper; diff --git a/src/main/java/com/extendedae_plus/mixin/emi/MixinRecipeFillButton.java b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinRecipeFillButton.java similarity index 95% rename from src/main/java/com/extendedae_plus/mixin/emi/MixinRecipeFillButton.java rename to src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinRecipeFillButton.java index d104bc5..6303c21 100644 --- a/src/main/java/com/extendedae_plus/mixin/emi/MixinRecipeFillButton.java +++ b/src/main/java/com/extendedae_plus/mixin/recipeViewer/emi/MixinRecipeFillButton.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.emi; +package com.extendedae_plus.mixin.recipeViewer.emi; import appeng.menu.me.items.PatternEncodingTermMenu; import dev.emi.emi.api.widget.RecipeFillButtonWidget; diff --git a/src/main/java/com/extendedae_plus/mixin/recipeViewer/jei/accessor/AccessorBookmarkOverlay.java b/src/main/java/com/extendedae_plus/mixin/recipeViewer/jei/accessor/AccessorBookmarkOverlay.java new file mode 100644 index 0000000..738ae8d --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/recipeViewer/jei/accessor/AccessorBookmarkOverlay.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.mixin.recipeViewer.jei.accessor; + +import mezz.jei.gui.bookmarks.BookmarkList; +import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(BookmarkOverlay.class) +public interface AccessorBookmarkOverlay { + @Accessor(value = "bookmarkList", remap = false) + BookmarkList eap$getBookmarkList(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java b/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java index 309547b..e2580a0 100644 --- a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java +++ b/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java @@ -1,7 +1,7 @@ package com.extendedae_plus.network; import com.extendedae_plus.ExtendedAEPlus; -import com.extendedae_plus.client.ui.ProviderSelectScreen; +import com.extendedae_plus.client.screen.ProviderSelectScreen; import com.mojang.serialization.Codec; import net.minecraft.client.Minecraft; import net.minecraft.network.RegistryFriendlyByteBuf; diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 4fdf7d9..11042ba 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -963,7 +963,7 @@ public class ExtendedAEPatternUploadUtil { return false; } // 读取已编码槽位的物品(通过 accessor) - var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) + var encodedSlot = ((PatternEncodingTermMenuAccessor) menu) .eap$getEncodedPatternSlot(); ItemStack stack = encodedSlot.getItem(); if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index d328408..729802f 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -28,10 +28,14 @@ "item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence", "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6Through ninefold sacrifice, the Void echoes§r—§8Iava, Lord of the Void§r, bestows upon thee this artifact", "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b—§4A §dUni§cverse §eWith§ain §6A §bSin§5gle §9Point", + "tooltip.extendedae_plus.encode_button_description": "\n§a[Ctrl] §7Upload patterns", + "screen.extendedae_plus.title": "ExtendedAE Plus Config", "config.extendedae_plus": "ExtendedAE Plus", "config.extendedae_plus.override_ae2wt_picking": "Override AE2WT Picking", + "config.extendedae_plus.independence_uploading_button": "Independence Upload Button", + "extendedae_plus.configuration.general": "General", "extendedae_plus.configuration.pageMultiplier": "Pattern Provider Page Multiplier", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index b254026..ef03eea 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -31,7 +31,7 @@ "item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静", "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物", "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰", - "tooltip.extendedae_plus.encode_button_description": "\n§a[Shift] §7上传样板", + "tooltip.extendedae_plus.encode_button_description": "\n§a[Ctrl] §7上传样板", "config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息", "config.jade.plugin_extendedae_plus.wt_frequency": "显示频率", @@ -46,6 +46,7 @@ "screen.extendedae_plus.title": "ExtendedAE Plus 配置", "config.extendedae_plus": "ExtendedAE Plus", "config.extendedae_plus.override_ae2wt_picking": "覆盖AE2WT选取", + "config.extendedae_plus.independence_uploading_button": "独立上传按钮", "extendedae_plus.configuration.general": "通用", "extendedae_plus.configuration.pageMultiplier": "扩展样板供应器槽位倍率", diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index bdeb9de..0fbef8e 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -26,15 +26,17 @@ "ae2.menu.PatternEncodingTermMenuMixin", "ae2.menu.PatternProviderMenuAdvancedMixin", "ae2.menu.PatternProviderMenuDoublingMixin", - "emi.MixinEncodePatternTransfer", - "emi.MixinRecipeFillButton", "extendedae.common.PartExPatternProviderMixin", "extendedae.common.TileExPatternProviderMixin", "extendedae.container.ContainerExPatternProviderMixin", "extendedae.container.ContainerExPatternTerminalMixin", - "extendedae.container.ContainerWirelessExPatternTerminalMixin" + "extendedae.container.ContainerWirelessExPatternTerminalMixin", + "recipeViewer.emi.MixinEncodePatternTransfer", + "recipeViewer.emi.MixinRecipeFillButton", + "recipeViewer.jei.accessor.AccessorBookmarkOverlay" ], "client": [ + "ModelBakeryMixin", "PickFromWirelessMixin", "accessor.AbstractContainerScreenAccessor", "accessor.ScreenAccessor", @@ -52,14 +54,13 @@ "ae2.client.gui.PatternProviderScreenMixin", "ae2.client.gui.SlotGridLayoutMixin", "ae2.menu.CraftConfirmMenuGoBackMixin", - "emi.MixinBoMScreen", - "emi.MixinEncodingHelper", "extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", "extendedae.client.HighlightButtonMixin", "extendedae.client.gui.GuiExPatternProviderMixin", "extendedae.client.gui.GuiExPatternTerminalMixin", - "hooks.ModelBakeryMixin" + "recipeViewer.MixinEncodingHelper", + "recipeViewer.emi.MixinBoMScreen" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml index e6de95f..2dcd140 100644 --- a/src/main/templates/META-INF/neoforge.mods.toml +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -95,6 +95,13 @@ config="${mod_id}.mixins.json" ordering="AFTER" side="BOTH" +[[dependencies.${mod_id}]] +modId="expandedae" +type="discouraged" +versionRange="*" +ordering="AFTER" +side="BOTH" + # Features are specific properties of the game environment, that you may want to declare you require. This example declares # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't # stop your mod loading on the server for example.