样板工具提示现在会显示加工需要的机器

- 在编码样板的悬停文本中,新增了对其加工机器的显示。
- 重构了代码,将与 EMI 相关的功能剥离到独立文件。
- 增加了对 EMI 是否存在的检查,以避免在未安装 EMI 时出现兼容性问题。
- 在非样板编码终端界面使用 EMI 配方填充时,将不会再向服务端发送配方 ID。
This commit is contained in:
link-fgfgui 2026-03-15 17:10:12 +08:00
parent 4dab5b82ef
commit 3e59f35774
9 changed files with 109 additions and 60 deletions

View File

@ -0,0 +1,7 @@
package com.extendedae_plus.integration.jei;
import net.neoforged.fml.ModList;
public final class EmiRuntimeProxy {
public static boolean isInstalled = ModList.get().isLoaded("emi");
}

View File

@ -22,6 +22,10 @@ public class ExtendedAEPlusMixinPlugin implements IMixinConfigPlugin {
return isClassPresent("mezz.jei.api.IModPlugin"); return isClassPresent("mezz.jei.api.IModPlugin");
} }
private static boolean isEmiPresent() {
return isClassPresent("dev.emi.emi.api.EmiPlugin");
}
private static boolean isAdvancedAePresent() { private static boolean isAdvancedAePresent() {
return isClassPresent("net.pedroksl.advanced_ae.AdvancedAE"); return isClassPresent("net.pedroksl.advanced_ae.AdvancedAE");
} }
@ -51,6 +55,9 @@ public class ExtendedAEPlusMixinPlugin implements IMixinConfigPlugin {
if (mixinClassName.startsWith("com.extendedae_plus.mixin.jei")) return false; if (mixinClassName.startsWith("com.extendedae_plus.mixin.jei")) return false;
if (mixinClassName.equals("com.extendedae_plus.mixin.ae2.menu.CraftConfirmMenuGoBackMixin")) return false; if (mixinClassName.equals("com.extendedae_plus.mixin.ae2.menu.CraftConfirmMenuGoBackMixin")) return false;
} }
if (!isEmiPresent()){
if (mixinClassName.startsWith("com.extendedae_plus.mixin.emi")) return false;
}
if (!isAdvancedAePresent()) { if (!isAdvancedAePresent()) {
if (mixinClassName.equals("com.extendedae_plus.mixin.advancedae.compat.PatternProviderLogicVirtualCompletionMixin")) { if (mixinClassName.equals("com.extendedae_plus.mixin.advancedae.compat.PatternProviderLogicVirtualCompletionMixin")) {
return false; return false;

View File

@ -2,10 +2,11 @@ package com.extendedae_plus.mixin.ae2;
import appeng.crafting.pattern.EncodedPatternItem; import appeng.crafting.pattern.EncodedPatternItem;
import com.extendedae_plus.config.ModConfigs; import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.integration.jei.EmiRuntimeProxy;
import com.extendedae_plus.util.RecipeFinderUtilEMI;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.TooltipFlag;
@ -19,7 +20,7 @@ import java.util.List;
@Mixin(EncodedPatternItem.class) @Mixin(EncodedPatternItem.class)
public class EncodedPatternItemMixin { public class EncodedPatternItemMixin {
// 客户端 HoverText 显示样板的编码玩家 // 客户端 HoverText 显示样板的编码玩家 加工机器
@Inject(method = "appendHoverText", at = @At("TAIL")) @Inject(method = "appendHoverText", at = @At("TAIL"))
public void epp$appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips, CallbackInfo ci){ public void epp$appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips, CallbackInfo ci){
if (ModConfigs.SHOW_ENCODER_PATTERN_PLAYER.get()) { if (ModConfigs.SHOW_ENCODER_PATTERN_PLAYER.get()) {
@ -29,11 +30,11 @@ public class EncodedPatternItemMixin {
String name = tag.getString("encodePlayer"); String name = tag.getString("encodePlayer");
lines.add(Component.translatable("extendedae_plus.pattern.hovertext.player", name).withStyle(ChatFormatting.GRAY)); lines.add(Component.translatable("extendedae_plus.pattern.hovertext.player", name).withStyle(ChatFormatting.GRAY));
} }
if (tag.contains("recipeId")) { if (EmiRuntimeProxy.isInstalled && tag.contains("recipeId")) {
String id = tag.getString("recipeId"); Component c = RecipeFinderUtilEMI.getWorkstationComponentByRecipeId(tag.getString("recipeId"));
ResourceLocation location = ResourceLocation.tryParse(id); if (c != null) {
if (location == null) return; lines.add(Component.translatable("extendedae_plus.pattern.hovertext.workstation", c).withStyle(ChatFormatting.GRAY));
lines.add(Component.translatable("extendedae_plus.pattern.hovertext.player", Component.translatable("recipe." + location.getNamespace() + "." + location.getPath())).withStyle(ChatFormatting.GRAY)); }
} }
} }
} }

View File

@ -1,18 +1,16 @@
package com.extendedae_plus.mixin.ae2.menu; package com.extendedae_plus.mixin.ae2.menu;
import appeng.api.crafting.PatternDetailsHelper; import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.stacks.GenericStack;
import appeng.menu.me.items.PatternEncodingTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot; import appeng.menu.slot.RestrictedInputSlot;
import appeng.parts.encoding.EncodingMode; import appeng.parts.encoding.EncodingMode;
import com.extendedae_plus.api.upload.IPatternEncodingIdSync; import com.extendedae_plus.api.upload.IPatternEncodingIdSync;
import com.extendedae_plus.api.upload.IPatternEncodingShiftUploadSync; import com.extendedae_plus.api.upload.IPatternEncodingShiftUploadSync;
import com.extendedae_plus.integration.jei.EmiRuntimeProxy;
import com.extendedae_plus.util.RecipeFinderUtilEMI; import com.extendedae_plus.util.RecipeFinderUtilEMI;
import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil; import com.extendedae_plus.util.uploadPattern.ExtendedAEPatternUploadUtil;
import com.glodblock.github.glodium.network.packet.sync.ActionMap; import com.glodblock.github.glodium.network.packet.sync.ActionMap;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder; import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.stack.EmiStack;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@ -28,8 +26,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
/** /**
* AE2 PatternEncodingTermMenu 增加一个通用动作持有者实现接收 EPP CGenericPacket 动作 * AE2 PatternEncodingTermMenu 增加一个通用动作持有者实现接收 EPP CGenericPacket 动作
* 注册动作 "upload_to_matrix"仅上传合成图样 ExtendedAE 装配矩阵 * 注册动作 "upload_to_matrix"仅上传合成图样 ExtendedAE 装配矩阵
@ -155,20 +151,10 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
if (itemStack != null && !itemStack.isEmpty()) { if (itemStack != null && !itemStack.isEmpty()) {
CustomData.update(DataComponents.CUSTOM_DATA, itemStack, tag -> { CustomData.update(DataComponents.CUSTOM_DATA, itemStack, tag -> {
tag.putString("encodePlayer", this.epp$player.getGameProfile().getName()); tag.putString("encodePlayer", this.epp$player.getGameProfile().getName());
if (this.eap$pendingRecipeIdUpload !=null) { if (EmiRuntimeProxy.isInstalled && this.eap$pendingRecipeIdUpload != null) {
EmiRecipe recipe = RecipeFinderUtilEMI.findRecipeById(this.eap$pendingRecipeIdUpload); if (RecipeFinderUtilEMI.isRecipeEqualToPattern(itemStack, this.eap$pendingRecipeIdUpload, this.epp$player.level())) {
// 检查样板与配方是否对应
List<GenericStack> stacks = PatternDetailsHelper.decodePattern(itemStack, this.epp$player.level()).getOutputs();
if (stacks != null && recipe != null) {
List<EmiStack> stacks2 = recipe.getOutputs();
List<ResourceLocation> ids1 = stacks.stream().map(s -> s.what().getId()).sorted().toList();
List<ResourceLocation> ids2 = stacks2.stream().map(EmiStack::getId).sorted().toList();
if (ids1.equals(ids2)) {
tag.putString("recipeId", this.eap$pendingRecipeIdUpload.toString()); tag.putString("recipeId", this.eap$pendingRecipeIdUpload.toString());
} }
}
this.eap$pendingRecipeIdUpload = null; this.eap$pendingRecipeIdUpload = null;
} }
}); });

View File

@ -1,5 +1,6 @@
package com.extendedae_plus.mixin.emi; package com.extendedae_plus.mixin.emi;
import appeng.client.gui.me.items.PatternEncodingTermScreen;
import com.extendedae_plus.network.upload.EncodeWithRecipeIdC2SPacket; import com.extendedae_plus.network.upload.EncodeWithRecipeIdC2SPacket;
import dev.emi.emi.api.recipe.EmiRecipe; import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.handler.EmiCraftContext; import dev.emi.emi.api.recipe.handler.EmiCraftContext;
@ -16,6 +17,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
public class EmiRecipeFillerMixin { public class EmiRecipeFillerMixin {
@Inject(method = "performFill", at = @At("RETURN"), remap = false) @Inject(method = "performFill", at = @At("RETURN"), remap = false)
private static <T extends AbstractContainerMenu> void onPerformFill(EmiRecipe recipe, AbstractContainerScreen<T> screen, EmiCraftContext.Type type, EmiCraftContext.Destination destination, int amount, CallbackInfoReturnable<Boolean> cir) { private static <T extends AbstractContainerMenu> void onPerformFill(EmiRecipe recipe, AbstractContainerScreen<T> screen, EmiCraftContext.Type type, EmiCraftContext.Destination destination, int amount, CallbackInfoReturnable<Boolean> cir) {
if (screen instanceof PatternEncodingTermScreen) {
PacketDistributor.sendToServer(new EncodeWithRecipeIdC2SPacket(recipe.getId())); PacketDistributor.sendToServer(new EncodeWithRecipeIdC2SPacket(recipe.getId()));
} }
} }
}

View File

@ -1,52 +1,71 @@
package com.extendedae_plus.util; package com.extendedae_plus.util;
import appeng.api.stacks.AEFluidKey; import appeng.api.crafting.IPatternDetails;
import appeng.api.stacks.AEItemKey; import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.stacks.GenericStack; import appeng.api.stacks.GenericStack;
import com.extendedae_plus.integration.jei.JeiRuntimeProxy; import com.mojang.logging.LogUtils;
import dev.emi.emi.api.EmiApi; import dev.emi.emi.api.EmiApi;
import dev.emi.emi.api.recipe.EmiRecipe; import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeManager; import dev.emi.emi.api.recipe.EmiRecipeManager;
import mezz.jei.api.constants.RecipeTypes; import dev.emi.emi.api.stack.EmiIngredient;
import mezz.jei.api.constants.VanillaTypes; import dev.emi.emi.api.stack.EmiStack;
import mezz.jei.api.gui.IRecipeLayoutDrawable; import net.minecraft.network.chat.Component;
import mezz.jei.api.gui.ingredient.IRecipeSlotView;
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
import mezz.jei.api.helpers.IJeiHelpers;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.neoforge.NeoForgeTypes;
import mezz.jei.api.recipe.*;
import mezz.jei.api.recipe.category.IRecipeCategory;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe; import net.minecraft.world.level.Level;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.neoforged.neoforge.fluids.FluidStack;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* 基于 EMI 查找配方提取输入输出槽位与数量信息 * 基于 EMI 查找配方
*/ */
public final class RecipeFinderUtilEMI { public final class RecipeFinderUtilEMI {
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder"); private static final Logger LOGGER = LogUtils.getLogger();
static EmiRecipeManager manager = EmiApi.getRecipeManager(); public static EmiRecipeManager manager = EmiApi.getRecipeManager();
private RecipeFinderUtilEMI() {
}
@Nullable @Nullable
public static EmiRecipe findRecipeById(ResourceLocation location) { public static EmiRecipe findRecipeById(ResourceLocation location) {
return manager.getRecipe(location); return manager.getRecipe(location);
} }
@Nullable @Nullable
public static EmiRecipe findRecipeById(String id) { public static EmiRecipe findRecipeById(String id) {
return findRecipeById(ResourceLocation.parse(id)); return findRecipeById(ResourceLocation.parse(id));
} }
@Nullable
public static Component getWorkstationComponentByRecipeId(String id) {
EmiRecipe recipe = RecipeFinderUtilEMI.findRecipeById(id);
if (recipe != null) {
List<EmiIngredient> workstations = EmiApi.getRecipeManager().getWorkstations(recipe.getCategory());
Component c;
if (!workstations.isEmpty()) {
c = workstations.getFirst().getEmiStacks().getFirst().getName();
} else {
c = recipe.getCategory().getName();
}
return c;
}
return null;
}
public static boolean isRecipeEqualToPattern(ItemStack itemStack, ResourceLocation location, Level level) {
EmiRecipe recipe = findRecipeById(location);
// 检查样板与配方是否对应
IPatternDetails pattern = PatternDetailsHelper.decodePattern(itemStack, level);
if (pattern != null) {
List<GenericStack> stacks = pattern.getOutputs();
if (stacks != null && recipe != null) {
List<EmiStack> stacks2 = recipe.getOutputs();
List<ResourceLocation> ids1 = stacks.stream().map(s -> s.what().getId()).sorted().toList();
List<ResourceLocation> ids2 = stacks2.stream().map(EmiStack::getId).sorted().toList();
return ids1.equals(ids2);
}
}
return false;
}
} }

View File

@ -0,0 +1,24 @@
package com.extendedae_plus.util;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
/**
* 基于 EMI 查找配方提取输入输出槽位与数量信息
*/
public final class RecipeFinderUtilJEI {
private static final Logger LOGGER = LogUtils.getLogger();
// static EmiRecipeManager manager = EmiApi.getRecipeManager();
private RecipeFinderUtilJEI() {
}
//
// @Nullable
// public static EmiRecipe findRecipeById(ResourceLocation location) {
// return manager.getRecipe(location);
// }
// @Nullable
// public static EmiRecipe findRecipeById(String id) {
// return findRecipeById(ResourceLocation.parse(id));
// }
}

View File

@ -71,6 +71,7 @@
"message.extendedae_plus.pattern_creation_failed": "Failed to create encoded pattern.", "message.extendedae_plus.pattern_creation_failed": "Failed to create encoded pattern.",
"message.extendedae_plus.no_network": "Unable to access AE network.", "message.extendedae_plus.no_network": "Unable to access AE network.",
"extendedae_plus.pattern.hovertext.player": "Encoded by %s", "extendedae_plus.pattern.hovertext.player": "Encoded by %s",
"extendedae_plus.pattern.hovertext.workstation": "Craftable by %s",
"item.extendedae_plus.entity_speed_ticker": "Entity Accelerator", "item.extendedae_plus.entity_speed_ticker": "Entity Accelerator",
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§lMachine Has Been Disabled", "screen.extendedae_plus.entity_speed_ticker.enable": "§c§lMachine Has Been Disabled",

View File

@ -71,6 +71,7 @@
"message.extendedae_plus.pattern_creation_failed": "创建编码样板失败。", "message.extendedae_plus.pattern_creation_failed": "创建编码样板失败。",
"message.extendedae_plus.no_network": "无法访问 AE 网络。", "message.extendedae_plus.no_network": "无法访问 AE 网络。",
"extendedae_plus.pattern.hovertext.player": "由 %s 编码", "extendedae_plus.pattern.hovertext.player": "由 %s 编码",
"extendedae_plus.pattern.hovertext.workstation": "可由 %s 加工",
"item.extendedae_plus.entity_speed_ticker": "实体加速器", "item.extendedae_plus.entity_speed_ticker": "实体加速器",
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用", "screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",