为编码样板添加 EMI 配方 ID 记录

在通过 EMI 填充配方并编码为样板时,记录下对应的 EMI 配方 ID。

- 当使用 EMI 的配方填充功能时,会发送一个包含配方 ID 的数据包到服务端。
- 在编码样板时,将此配方 ID 存储在样板物品的 NBT 数据中。
- 在样板的悬浮提示中显示其关联的配方名称,以便追溯。
This commit is contained in:
link-fgfgui 2026-03-15 13:32:55 +08:00
parent 7636fff3b4
commit 4dab5b82ef
8 changed files with 176 additions and 3 deletions

View File

@ -0,0 +1,10 @@
package com.extendedae_plus.api.upload;
import net.minecraft.resources.ResourceLocation;
public interface IPatternEncodingIdSync {
/**
* 由客户端发送的编码指令附带的 recipe id
*/
void eap$clientRecipeIdUpload(ResourceLocation id);
}

View File

@ -3,6 +3,7 @@ package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.network.*; import com.extendedae_plus.network.*;
import com.extendedae_plus.network.packet.EAPConfigButtonPacket; import com.extendedae_plus.network.packet.EAPConfigButtonPacket;
import com.extendedae_plus.network.upload.EncodeWithRecipeIdC2SPacket;
import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket; import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
@ -26,6 +27,7 @@ public class ModNetwork {
registrar.playToServer(CreateAndUploadPatternC2SPacket.TYPE, CreateAndUploadPatternC2SPacket.STREAM_CODEC, CreateAndUploadPatternC2SPacket::handle); registrar.playToServer(CreateAndUploadPatternC2SPacket.TYPE, CreateAndUploadPatternC2SPacket.STREAM_CODEC, CreateAndUploadPatternC2SPacket::handle);
registrar.playToServer(CancelPendingPatternC2SPacket.TYPE,CancelPendingPatternC2SPacket.STREAM_CODEC,CancelPendingPatternC2SPacket::handle); registrar.playToServer(CancelPendingPatternC2SPacket.TYPE,CancelPendingPatternC2SPacket.STREAM_CODEC,CancelPendingPatternC2SPacket::handle);
registrar.playToServer(EncodeWithShiftFlagC2SPacket.TYPE, EncodeWithShiftFlagC2SPacket.STREAM_CODEC, EncodeWithShiftFlagC2SPacket::handle); registrar.playToServer(EncodeWithShiftFlagC2SPacket.TYPE, EncodeWithShiftFlagC2SPacket.STREAM_CODEC, EncodeWithShiftFlagC2SPacket::handle);
registrar.playToServer(EncodeWithRecipeIdC2SPacket.TYPE, EncodeWithRecipeIdC2SPacket.STREAM_CODEC, EncodeWithRecipeIdC2SPacket::handle);
// 新增JEI 中键打开合成界面 & 无线终端拾取方块物品 // 新增JEI 中键打开合成界面 & 无线终端拾取方块物品
registrar.playToServer(com.extendedae_plus.network.OpenCraftFromJeiC2SPacket.TYPE, registrar.playToServer(com.extendedae_plus.network.OpenCraftFromJeiC2SPacket.TYPE,
com.extendedae_plus.network.OpenCraftFromJeiC2SPacket.STREAM_CODEC, com.extendedae_plus.network.OpenCraftFromJeiC2SPacket.STREAM_CODEC,

View File

@ -5,6 +5,7 @@ import com.extendedae_plus.config.ModConfigs;
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;
@ -28,6 +29,12 @@ 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")) {
String id = tag.getString("recipeId");
ResourceLocation location = ResourceLocation.tryParse(id);
if (location == null) return;
lines.add(Component.translatable("extendedae_plus.pattern.hovertext.player", Component.translatable("recipe." + location.getNamespace() + "." + location.getPath())).withStyle(ChatFormatting.GRAY));
}
} }
} }
} }

View File

@ -1,14 +1,20 @@
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.IPatternEncodingShiftUploadSync; import com.extendedae_plus.api.upload.IPatternEncodingShiftUploadSync;
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.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -22,13 +28,14 @@ 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 装配矩阵
*/ */
@Mixin(PatternEncodingTermMenu.class) @Mixin(PatternEncodingTermMenu.class)
public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHolder, IPatternEncodingShiftUploadSync { public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHolder, IPatternEncodingShiftUploadSync, IPatternEncodingIdSync {
@Unique @Unique
private final ActionMap eap$actions = ActionMap.create(); private final ActionMap eap$actions = ActionMap.create();
@ -38,6 +45,9 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
@Unique @Unique
private boolean eap$pendingShiftUpload; private boolean eap$pendingShiftUpload;
@Unique
private ResourceLocation eap$pendingRecipeIdUpload;
@Shadow(remap = false) @Shadow(remap = false)
private RestrictedInputSlot encodedPatternSlot; private RestrictedInputSlot encodedPatternSlot;
@ -55,6 +65,11 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo
return flag; return flag;
} }
@Unique
@Override
public void eap$clientRecipeIdUpload(ResourceLocation id) {
this.eap$pendingRecipeIdUpload = id;
}
@Unique @Unique
private void eap$scheduleUploadWithRetry(ServerPlayer sp, PatternEncodingTermMenu menu, int attemptsLeft) { private void eap$scheduleUploadWithRetry(ServerPlayer sp, PatternEncodingTermMenu menu, int attemptsLeft) {
sp.server.execute(() -> { sp.server.execute(() -> {
@ -140,7 +155,24 @@ 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) {
EmiRecipe recipe = RecipeFinderUtilEMI.findRecipeById(this.eap$pendingRecipeIdUpload);
// 检查样板与配方是否对应
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());
}
}
this.eap$pendingRecipeIdUpload =null;
}
}); });
cir.setReturnValue(itemStack); cir.setReturnValue(itemStack);
} }
} }

View File

@ -0,0 +1,21 @@
package com.extendedae_plus.mixin.emi;
import com.extendedae_plus.network.upload.EncodeWithRecipeIdC2SPacket;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.handler.EmiCraftContext;
import dev.emi.emi.registry.EmiRecipeFiller;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.neoforged.neoforge.network.PacketDistributor;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(EmiRecipeFiller.class)
public class EmiRecipeFillerMixin {
@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) {
PacketDistributor.sendToServer(new EncodeWithRecipeIdC2SPacket(recipe.getId()));
}
}

View File

@ -0,0 +1,49 @@
package com.extendedae_plus.network.upload;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.api.upload.IPatternEncodingIdSync;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S图样编码终端点击编码按钮时上传客户端的配方ID
*/
public class EncodeWithRecipeIdC2SPacket implements CustomPacketPayload {
public static final Type<EncodeWithRecipeIdC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "encode_with_recipe"));
public static final StreamCodec<FriendlyByteBuf, EncodeWithRecipeIdC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> buf.writeResourceLocation(pkt.id),
buf -> new EncodeWithRecipeIdC2SPacket(buf.readResourceLocation())
);
private final ResourceLocation id;
public EncodeWithRecipeIdC2SPacket(ResourceLocation id) {
this.id = id;
}
public static void handle(final EncodeWithRecipeIdC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) {
return;
}
if (player.containerMenu instanceof PatternEncodingTermMenu menu
&& menu instanceof IPatternEncodingIdSync sync) {
sync.eap$clientRecipeIdUpload(msg.id);
}
});
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}

View File

@ -0,0 +1,52 @@
package com.extendedae_plus.util;
import appeng.api.stacks.AEFluidKey;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.GenericStack;
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
import dev.emi.emi.api.EmiApi;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeManager;
import mezz.jei.api.constants.RecipeTypes;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.gui.IRecipeLayoutDrawable;
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.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.neoforged.neoforge.fluids.FluidStack;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 基于 EMI 查找配方提取输入输出槽位与数量信息
*/
public final class RecipeFinderUtilEMI {
private static final Logger LOGGER = LoggerFactory.getLogger("ExtendedAE Plus - RecipeFinder");
static EmiRecipeManager manager = EmiApi.getRecipeManager();
private RecipeFinderUtilEMI() {
}
@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

@ -53,10 +53,10 @@
"ae2.parts.automation.IOBusPartChannelCardMixin", "ae2.parts.automation.IOBusPartChannelCardMixin",
"ae2.parts.storagebus.StorageBusPartChannelCardMixin", "ae2.parts.storagebus.StorageBusPartChannelCardMixin",
"ae2WTlib.ContainerUWirelessExPatternTerminalMixin", "ae2WTlib.ContainerUWirelessExPatternTerminalMixin",
"emi.EmiRecipeFillerMixin",
"extendedae.common.PartExPatternProviderMixin", "extendedae.common.PartExPatternProviderMixin",
"extendedae.common.TileExPatternProviderMixin", "extendedae.common.TileExPatternProviderMixin",
"extendedae.common.matrix.ClusterAssemblerMatrixMixin", "extendedae.common.matrix.ClusterAssemblerMatrixMixin",
"extendedae.common.matrix.ClusterAssemblerMatrixMixin",
"extendedae.container.ContainerAssemblerMatrixPatternSlotTrackerMixin", "extendedae.container.ContainerAssemblerMatrixPatternSlotTrackerMixin",
"extendedae.container.ContainerExPatternProviderMixin", "extendedae.container.ContainerExPatternProviderMixin",
"extendedae.container.ContainerExPatternTerminalMixin", "extendedae.container.ContainerExPatternTerminalMixin",