From b87b74929eab8f72b988c48158de2711f384dd7f Mon Sep 17 00:00:00 2001 From: GaLi <3096147684@qq.com> Date: Sat, 28 Feb 2026 17:59:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E4=B9=A6=E7=AD=BE=E5=88=86?= =?UTF-8?q?=E6=94=AF=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/event/CtrlQPatternKeyHandler.java | 110 ++++++++- ...loadEncodedPatternToProviderC2SPacket.java | 28 ++- .../pattern/CreateCtrlQPatternC2SPacket.java | 230 +++++++----------- .../RequestProvidersListC2SPacket.java | 22 ++ .../util/PatternTerminalUtil.java | 22 +- .../uploadPattern/ProviderUploadUtil.java | 171 +++++++++++++ 6 files changed, 423 insertions(+), 160 deletions(-) diff --git a/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java b/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java index b2355f4..e00b6b2 100644 --- a/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java +++ b/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java @@ -5,8 +5,10 @@ import com.extendedae_plus.client.ModKeybindings; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.integration.jei.JeiRuntimeProxy; import com.extendedae_plus.network.pattern.CreateCtrlQPatternC2SPacket; +import com.extendedae_plus.network.provider.RequestProvidersListC2SPacket; import com.extendedae_plus.util.RecipeFinderUtil; import com.extendedae_plus.util.RecipeInfo; +import com.extendedae_plus.util.uploadPattern.RecipeTypeNameConfig; import mezz.jei.api.constants.RecipeTypes; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.ingredients.ITypedIngredient; @@ -15,6 +17,7 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -273,7 +276,112 @@ public class CtrlQPatternKeyHandler { * @param recipeBookmark 配方书签对象 */ private static void handleProcessingRecipeBookmark(Object recipeBookmark) { - System.out.println("chuli"); + try { + var getRecipeUidMethod = recipeBookmark.getClass().getMethod("getRecipeUid"); + net.minecraft.resources.ResourceLocation recipeId = + (net.minecraft.resources.ResourceLocation) getRecipeUidMethod.invoke(recipeBookmark); + if (recipeId == null) { + return; + } + + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) { + return; + } + + var recipeManager = mc.level.getRecipeManager(); + var recipeOpt = recipeManager.byKey(recipeId); + if (recipeOpt.isEmpty()) { + if (mc.player != null) { + mc.player.displayClientMessage( + Component.translatable("message.extendedae_plus.recipe_not_found"), + true + ); + } + return; + } + + Object recipeBase = null; + try { + var getRecipeMethod = recipeBookmark.getClass().getMethod("getRecipe"); + recipeBase = getRecipeMethod.invoke(recipeBookmark); + } catch (Throwable ignored) { + } + setLastProcessingNameFromRecipe(recipeBase != null ? recipeBase : recipeOpt.get()); + + List recipeInfos = RecipeFinderUtil.findRecipesByIngredient( + JeiRuntimeProxy.getIngredientUnderMouse().orElse(null) + ); + + if (recipeInfos.isEmpty()) { + var getRecipeOutputMethod = recipeBookmark.getClass().getMethod("getRecipeOutput"); + Object recipeOutput = getRecipeOutputMethod.invoke(recipeBookmark); + if (recipeOutput instanceof ITypedIngredient typedIngredient) { + recipeInfos = RecipeFinderUtil.findRecipesByIngredient(typedIngredient); + } + } + + if (recipeInfos.isEmpty()) { + if (mc.player != null) { + mc.player.displayClientMessage( + Component.translatable("message.extendedae_plus.no_recipes_found"), + true + ); + } + return; + } + + RecipeInfo matchingRecipeInfo = null; + for (RecipeInfo info : recipeInfos) { + if (info.getRecipe().getId().equals(recipeId)) { + matchingRecipeInfo = info; + break; + } + } + if (matchingRecipeInfo == null) { + matchingRecipeInfo = recipeInfos.get(0); + } + + List selectedIngredients = selectIngredientsWithJeiPriority(matchingRecipeInfo); + List selectedOutputs = convertOutputsToItemStacks(matchingRecipeInfo); + + ModNetwork.CHANNEL.sendToServer(new CreateCtrlQPatternC2SPacket( + recipeId, + matchingRecipeInfo.isCraftingRecipe(), + selectedIngredients, + selectedOutputs, + true + )); + + ModNetwork.CHANNEL.sendToServer(new RequestProvidersListC2SPacket()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void setLastProcessingNameFromRecipe(Object recipeBase) { + String name = null; + if (recipeBase instanceof Recipe recipe) { + name = RecipeTypeNameConfig.mapRecipeTypeToSearchKey(recipe); + } else if (recipeBase != null + && "com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) { + name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(recipeBase); + } else if (recipeBase != null + && "com.gregtechceu.gtceu.integration.jei.recipe.GTRecipeWrapper".equals(recipeBase.getClass().getName())) { + try { + var field = recipeBase.getClass().getField("recipe"); + Object inner = field.get(recipeBase); + name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(inner); + } catch (Throwable ignored) { + } + } + + if (name == null || name.isBlank()) { + name = RecipeTypeNameConfig.deriveSearchKeyFromUnknownRecipe(recipeBase); + } + if (name != null && !name.isBlank()) { + RecipeTypeNameConfig.setLastProcessingName(name); + } } /** diff --git a/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java b/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java index 2f6ccb3..b0018ec 100644 --- a/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java @@ -9,7 +9,7 @@ import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; /** - * C2S: 请求将图样编码终端的已编码样板上传到指定的样板供应器(由客户端选择)。 + * C2S: Request uploading an encoded pattern to a selected provider. */ public class UploadEncodedPatternToProviderC2SPacket { private final long providerId; @@ -31,15 +31,23 @@ public class UploadEncodedPatternToProviderC2SPacket { ctx.enqueueWork(() -> { ServerPlayer player = ctx.getSender(); if (player == null) return; - if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return; - // 支持两种模式: - // 1) providerId >= 0: 访问终端 byId 模式 - // 2) providerId < 0: 索引模式(由列表回退路径生成),index = -1 - providerId - if (msg.providerId >= 0) { - ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId); - } else { - int index = (int) (-1L - msg.providerId); - ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index); + + // Prefer pending Ctrl+Q pattern upload when present. + if (ProviderUploadUtil.hasPendingCtrlQPattern(player)) { + if (ProviderUploadUtil.uploadPendingCtrlQPattern(player, msg.providerId)) { + return; + } + } + + if (player.containerMenu instanceof PatternEncodingTermMenu menu) { + // 1) providerId >= 0: byId mode from access terminal + // 2) providerId < 0: index mode, index = -1 - providerId + if (msg.providerId >= 0) { + ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId); + } else { + int index = (int) (-1L - msg.providerId); + ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index); + } } }); ctx.setPacketHandled(true); diff --git a/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java b/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java index a1121b1..0f012ef 100644 --- a/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/pattern/CreateCtrlQPatternC2SPacket.java @@ -1,11 +1,5 @@ package com.extendedae_plus.network.pattern; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -import com.extendedae_plus.util.wireless.WirelessTerminalLocator; - import appeng.api.crafting.PatternDetailsHelper; import appeng.api.networking.IGrid; import appeng.api.networking.energy.IEnergyService; @@ -17,6 +11,8 @@ import appeng.core.definitions.AEItems; import appeng.items.tools.powered.WirelessCraftingTerminalItem; import appeng.items.tools.powered.WirelessTerminalItem; import appeng.me.helpers.PlayerSource; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; +import com.extendedae_plus.util.wireless.WirelessTerminalLocator; import de.mari_023.ae2wtlib.terminal.WTMenuHost; import de.mari_023.ae2wtlib.wut.WTDefinition; import de.mari_023.ae2wtlib.wut.WUTHandler; @@ -31,24 +27,31 @@ import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraftforge.network.NetworkEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + /** - * C2S: Ctrl+Q快速创建样板数据包 - * - *

- * 从客户端发送配方ID、选择的材料和输出到服务器,服务器消耗空白样板并创建编码样板掉落到玩家脚下

+ * C2S: Ctrl+Q quick-create pattern request. */ public class CreateCtrlQPatternC2SPacket { private final ResourceLocation recipeId; private final boolean isCraftingPattern; private final List selectedIngredients; - private final List outputs; // 输出材料(物品或包装的流体) + private final List outputs; + private final boolean openProviderSelector; public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List selectedIngredients, List outputs) { + this(recipeId, isCraftingPattern, selectedIngredients, outputs, false); + } + + public CreateCtrlQPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List selectedIngredients, List outputs, boolean openProviderSelector) { this.recipeId = recipeId; this.isCraftingPattern = isCraftingPattern; this.selectedIngredients = selectedIngredients; this.outputs = outputs; + this.openProviderSelector = openProviderSelector; } public static void encode(CreateCtrlQPatternC2SPacket msg, FriendlyByteBuf buf) { @@ -62,22 +65,27 @@ public class CreateCtrlQPatternC2SPacket { for (ItemStack stack : msg.outputs) { buf.writeItem(stack); } + buf.writeBoolean(msg.openProviderSelector); } public static CreateCtrlQPatternC2SPacket decode(FriendlyByteBuf buf) { ResourceLocation recipeId = buf.readResourceLocation(); boolean isCraftingPattern = buf.readBoolean(); + int ingredientCount = buf.readInt(); List ingredients = new ArrayList<>(); for (int i = 0; i < ingredientCount; i++) { ingredients.add(buf.readItem()); } + int outputCount = buf.readInt(); List outputs = new ArrayList<>(); for (int i = 0; i < outputCount; i++) { outputs.add(buf.readItem()); } - return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs); + + boolean openProviderSelector = buf.readableBytes() > 0 && buf.readBoolean(); + return new CreateCtrlQPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs, openProviderSelector); } public static void handle(CreateCtrlQPatternC2SPacket msg, Supplier ctxSupplier) { @@ -88,64 +96,49 @@ public class CreateCtrlQPatternC2SPacket { return; } - // 1. 验证配方存在 RecipeManager recipeManager = player.level().getRecipeManager(); var recipeOpt = recipeManager.byKey(msg.recipeId); - if (recipeOpt.isEmpty()) { - player.displayClientMessage( - Component.translatable("message.extendedae_plus.recipe_not_found"), - false - ); + player.displayClientMessage(Component.translatable("message.extendedae_plus.recipe_not_found"), false); return; } Recipe recipe = recipeOpt.get(); - // 2. 消耗空白样板 if (!consumeBlankPattern(player)) { - player.displayClientMessage( - Component.translatable("message.extendedae_plus.no_blank_pattern"), - false - ); + player.displayClientMessage(Component.translatable("message.extendedae_plus.no_blank_pattern"), false); return; } - // 3. 创建样板 ItemStack pattern = createPattern(recipe, msg.isCraftingPattern, msg.selectedIngredients, msg.outputs, player); - if (pattern.isEmpty()) { - // 创建失败,退还空白样板 player.getInventory().add(AEItems.BLANK_PATTERN.stack()); - player.displayClientMessage( - Component.translatable("message.extendedae_plus.pattern_creation_failed"), - false - ); + player.displayClientMessage(Component.translatable("message.extendedae_plus.pattern_creation_failed"), false); + return; + } + + if (msg.openProviderSelector) { + String pendingId = ProviderUploadUtil.beginPendingCtrlQUpload(player, pattern); + if (pendingId == null) { + if (!player.getInventory().add(pattern)) { + player.drop(pattern, false); + } + } return; } - // 4. 交付样板:优先放入背包,满了再掉落 if (!player.getInventory().add(pattern)) { player.drop(pattern, false); } - }); ctx.setPacketHandled(true); } - /** - * 消耗空白样板:优先从AE网络提取,网络无货才从玩家背包消耗 - * - * @param player 玩家 - * @return 是否成功消耗 - */ private static boolean consumeBlankPattern(ServerPlayer player) { - // 1. 尝试从AE网络提取(需要玩家持有无线终端) if (tryExtractFromNetwork(player)) { return true; } - // 2. 网络提取失败,从背包消耗 Inventory inventory = player.getInventory(); for (int i = 0; i < inventory.getContainerSize(); i++) { ItemStack stack = inventory.getItem(i); @@ -155,27 +148,19 @@ public class CreateCtrlQPatternC2SPacket { } } - return false; // 未找到 + return false; } - /** - * 尝试从AE网络提取空白样板 - * - * @param player 玩家 - * @return 是否成功提取 - */ private static boolean tryExtractFromNetwork(ServerPlayer player) { - // 定位玩家身上的无线终端 WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player); ItemStack terminal = located.stack; if (terminal.isEmpty()) { - return false; // 没有无线终端 + return false; } IGrid grid; boolean usedWtHost; - // 若来自 Curios:优先通过 ae2wtlib 的 WTMenuHost 获取量子桥网络 String curiosSlotId = located.getCuriosSlotId(); int curiosIndex = located.getCuriosIndex(); @@ -208,7 +193,6 @@ public class CreateCtrlQPatternC2SPacket { return false; } } else { - // 非 Curios:按 AE2 原生路径处理 WirelessCraftingTerminalItem wct = terminal.getItem() instanceof WirelessCraftingTerminalItem c ? c : null; WirelessTerminalItem wt = wct != null ? wct : (terminal.getItem() instanceof WirelessTerminalItem t ? t : null); if (wt == null) { @@ -219,60 +203,41 @@ public class CreateCtrlQPatternC2SPacket { return false; } if (!wt.hasPower(player, 0.5, terminal)) { - return false; // 能量不足 + return false; } usedWtHost = false; } - // 从网络提取空白样板 AEItemKey blankPatternKey = AEItemKey.of(AEItems.BLANK_PATTERN.stack()); IEnergyService energy = grid.getEnergyService(); MEStorage storage = grid.getStorageService().getInventory(); long extracted = StorageHelper.poweredExtraction( - energy, - storage, - blankPatternKey, - 1, // 只提取1个 - new PlayerSource(player) + energy, + storage, + blankPatternKey, + 1, + new PlayerSource(player) ); if (extracted > 0) { - // 提取成功,消耗无线终端能量 - if (usedWtHost) { - // WTMenuHost 已在 drainPower 中处理能量消耗 - } else { - // 原生 AE2 扣能 + if (!usedWtHost) { WirelessCraftingTerminalItem wct2 = terminal.getItem() instanceof WirelessCraftingTerminalItem c2 ? c2 : null; WirelessTerminalItem wt2 = wct2 != null ? wct2 : (terminal.getItem() instanceof WirelessTerminalItem t2 ? t2 : null); if (wt2 != null) { wt2.usePower(player, 0.5, terminal); } } - // 确保写回终端(若位于 Curios 等需要显式写回的容器) located.commit(); return true; } - return false; // 网络中没有空白样板 + return false; } - /** - * 从配方创建样板(支持物品和流体) - * - * @param recipe 配方 - * @param isCrafting 是否为合成样板 - * @param selectedIngredients 客户端选择的材料(应用JEI优先级后,流体已包装为 GenericStack.wrapInItemStack) - * @param selectedOutputs 客户端传递的输出材料(物品或包装的流体) - * @param player 玩家 - * @return 编码的样板物品 - */ private static ItemStack createPattern(Recipe recipe, boolean isCrafting, List selectedIngredients, List selectedOutputs, ServerPlayer player) { try { if (isCrafting && recipe instanceof CraftingRecipe craftingRecipe) { - // ===== 合成样板创建路径 ===== - - // 准备9格工作台输入(3x3布局) ItemStack[] inputs = new ItemStack[9]; for (int i = 0; i < 9; i++) { if (i < selectedIngredients.size()) { @@ -282,80 +247,59 @@ public class CreateCtrlQPatternC2SPacket { } } - // 准备输出 ItemStack output = recipe.getResultItem(player.level().registryAccess()).copy(); - - // 使用 encodeCraftingPattern 创建合成样板 - // 直接传递 CraftingRecipe 对象而非 RecipeHolder ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern( - craftingRecipe, - inputs, - output, - true, // allowSubstitutes - 允许替代材料 - false // allowFluidSubstitutes - 不允许流体替代 + craftingRecipe, + inputs, + output, + true, + false ); - // 添加编码玩家信息到NBT encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString()); - - return encodedPattern; - - } else { - // ===== 处理样板创建路径(支持物品和流体)===== - - List inputs = new ArrayList<>(); - List outputs = new ArrayList<>(); - - // 处理输入 - 使用客户端传入的材料选择(支持流体) - for (ItemStack item : selectedIngredients) { - if (!item.isEmpty()) { - // 尝试解包 GenericStack(流体会被包装在特殊的 ItemStack 中) - GenericStack genericStack = GenericStack.unwrapItemStack(item); - if (genericStack != null) { - // 这是一个包装的 GenericStack(可能是流体) - inputs.add(genericStack); - } else { - // 普通物品 - AEItemKey itemKey = AEItemKey.of(item); - if (itemKey != null) { - inputs.add(new GenericStack(itemKey, item.getCount())); - } - } - } - } - - // 处理输出 - 使用客户端传入的输出(支持流体) - for (ItemStack item : selectedOutputs) { - if (!item.isEmpty()) { - // 尝试解包 GenericStack(流体会被包装在特殊的 ItemStack 中) - GenericStack genericStack = GenericStack.unwrapItemStack(item); - if (genericStack != null) { - // 这是一个包装的 GenericStack(可能是流体) - outputs.add(genericStack); - } else { - // 普通物品 - AEItemKey itemKey = AEItemKey.of(item); - if (itemKey != null) { - outputs.add(new GenericStack(itemKey, item.getCount())); - } - } - } - } - - // 使用 encodeProcessingPattern 创建处理样板 - ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern( - inputs.toArray(new GenericStack[0]), - outputs.toArray(new GenericStack[0]) - ); - - // 添加编码玩家信息到NBT - encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString()); - return encodedPattern; } + List inputs = new ArrayList<>(); + List outputs = new ArrayList<>(); + + for (ItemStack item : selectedIngredients) { + if (!item.isEmpty()) { + GenericStack genericStack = GenericStack.unwrapItemStack(item); + if (genericStack != null) { + inputs.add(genericStack); + } else { + AEItemKey itemKey = AEItemKey.of(item); + if (itemKey != null) { + inputs.add(new GenericStack(itemKey, item.getCount())); + } + } + } + } + + for (ItemStack item : selectedOutputs) { + if (!item.isEmpty()) { + GenericStack genericStack = GenericStack.unwrapItemStack(item); + if (genericStack != null) { + outputs.add(genericStack); + } else { + AEItemKey itemKey = AEItemKey.of(item); + if (itemKey != null) { + outputs.add(new GenericStack(itemKey, item.getCount())); + } + } + } + } + + ItemStack encodedPattern = PatternDetailsHelper.encodeProcessingPattern( + inputs.toArray(new GenericStack[0]), + outputs.toArray(new GenericStack[0]) + ); + + encodedPattern.getOrCreateTag().putString("encodePlayer", player.getName().getString()); + return encodedPattern; } catch (Exception e) { return ItemStack.EMPTY; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java index 8a813c5..b99fc94 100644 --- a/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java @@ -6,6 +6,7 @@ import appeng.menu.me.items.PatternEncodingTermMenu; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.util.PatternProviderDataUtil; import com.extendedae_plus.util.PatternTerminalUtil; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; @@ -29,6 +30,27 @@ public class RequestProvidersListC2SPacket { ctx.enqueueWork(() -> { ServerPlayer player = ctx.getSender(); if (player == null) return; + + // Ctrl+Q pending 模式:不依赖编码终端,直接基于玩家网络给出列表(负数索引 ID) + if (ProviderUploadUtil.hasPendingCtrlQPattern(player)) { + List containers = ProviderUploadUtil.listAvailableProvidersFromPlayerNetwork(player); + List idxIds = new ArrayList<>(); + List names = new ArrayList<>(); + List slots = new ArrayList<>(); + for (int i = 0; i < containers.size(); i++) { + var c = containers.get(i); + if (c == null) continue; + int empty = PatternProviderDataUtil.getAvailableSlots(c); + if (empty <= 0) continue; + long encodedId = -1L - i; + idxIds.add(encodedId); + names.add(PatternProviderDataUtil.getProviderDisplayName(c)); + slots.add(empty); + } + ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT); + return; + } + if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return; // 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID) diff --git a/src/main/java/com/extendedae_plus/util/PatternTerminalUtil.java b/src/main/java/com/extendedae_plus/util/PatternTerminalUtil.java index 543dae1..8c9ef46 100644 --- a/src/main/java/com/extendedae_plus/util/PatternTerminalUtil.java +++ b/src/main/java/com/extendedae_plus/util/PatternTerminalUtil.java @@ -158,13 +158,20 @@ public final class PatternTerminalUtil { * 返回顺序稳定:按 grid 的 machineClasses 顺序,再按 activeMachines 迭代顺序。 */ public static List listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) { - List list = new ArrayList<>(); - if (menu == null) return list; + if (menu == null) return new ArrayList<>(); try { IGridNode node = menu.getNetworkNode(); - if (node == null) return list; - IGrid grid = node.getGrid(); - if (grid == null) return list; + if (node == null) return new ArrayList<>(); + return listAvailableProvidersFromGrid(node.getGrid()); + } catch (Throwable ignored) { + return new ArrayList<>(); + } + } + + public static List listAvailableProvidersFromGrid(IGrid grid) { + List list = new ArrayList<>(); + if (grid == null) return list; + try { for (var machineClass : grid.getMachineClasses()) { if (PatternContainer.class.isAssignableFrom(machineClass)) { @SuppressWarnings("unchecked") @@ -175,7 +182,10 @@ public final class PatternTerminalUtil { if (inv == null || inv.size() <= 0) continue; boolean hasEmpty = false; for (int i = 0; i < inv.size(); i++) { - if (inv.getStackInSlot(i).isEmpty()) { hasEmpty = true; break; } + if (inv.getStackInSlot(i).isEmpty()) { + hasEmpty = true; + break; + } } if (hasEmpty) list.add(container); } diff --git a/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java b/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java index eb3157e..99df4de 100644 --- a/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java @@ -2,7 +2,10 @@ package com.extendedae_plus.util.uploadPattern; import appeng.api.crafting.PatternDetailsHelper; import appeng.api.inventories.InternalInventory; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; import appeng.helpers.patternprovider.PatternContainer; +import appeng.items.tools.powered.WirelessTerminalItem; import appeng.menu.implementations.PatternAccessTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu; import appeng.util.inv.FilteredInternalInventory; @@ -10,10 +13,16 @@ import appeng.util.inv.filter.IAEItemFilter; import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor; import com.extendedae_plus.util.PatternProviderDataUtil; import com.extendedae_plus.util.PatternTerminalUtil; +import com.extendedae_plus.util.wireless.WirelessTerminalLocator; +import de.mari_023.ae2wtlib.terminal.WTMenuHost; +import de.mari_023.ae2wtlib.wut.WTDefinition; +import de.mari_023.ae2wtlib.wut.WUTHandler; +import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import java.util.List; +import java.util.UUID; /** * 与样板供应器(provider)上传相关的工具类: @@ -24,6 +33,9 @@ import java.util.List; * 其中使用 PatternTerminalUtil 提供的反射/容器访问工具。 */ public final class ProviderUploadUtil { + private static final String PENDING_DATA_KEY = "eap_ctrlq_pending_provider_upload_id"; + private static final String PENDING_STACK_KEY = "eap_ctrlq_pending_provider_upload_stack"; + private ProviderUploadUtil() {} /** @@ -221,6 +233,165 @@ public final class ProviderUploadUtil { return false; } + /** + * 缓存 Ctrl+Q 生成的待上传样板(不放入玩家背包)。 + */ + public static String beginPendingCtrlQUpload(ServerPlayer player, ItemStack pattern) { + if (player == null || pattern == null || pattern.isEmpty() || !PatternDetailsHelper.isEncodedPattern(pattern)) { + return null; + } + clearPendingCtrlQUpload(player); + String id = UUID.randomUUID().toString(); + player.getPersistentData().putString(PENDING_DATA_KEY, id); + player.getPersistentData().put(PENDING_STACK_KEY, pattern.copy().save(new CompoundTag())); + return id; + } + + public static void clearPendingCtrlQUpload(ServerPlayer player) { + if (player == null) return; + player.getPersistentData().remove(PENDING_DATA_KEY); + player.getPersistentData().remove(PENDING_STACK_KEY); + } + + public static boolean hasPendingCtrlQPattern(ServerPlayer player) { + if (player == null) return false; + String id = player.getPersistentData().getString(PENDING_DATA_KEY); + if (id == null || id.isBlank()) return false; + return !getPendingCtrlQPattern(player).isEmpty(); + } + + /** + * 将 pending Ctrl+Q 样板上传到玩家网络中的目标 provider(负数索引 ID)。 + */ + public static boolean uploadPendingCtrlQPattern(ServerPlayer player, long providerId) { + if (player == null) return false; + ItemStack pending = getPendingCtrlQPattern(player); + if (pending.isEmpty()) return false; + + ItemStack remain = insertPatternIntoProviderFromPlayerNetwork(player, pending, providerId); + if (remain.getCount() >= pending.getCount()) { + return false; + } + + if (remain.isEmpty()) { + clearPendingCtrlQUpload(player); + } else { + player.getPersistentData().put(PENDING_STACK_KEY, remain.save(new CompoundTag())); + } + return true; + } + + /** + * 列出玩家无线终端网络中的可用 provider,顺序与负数索引上传保持一致。 + */ + public static List listAvailableProvidersFromPlayerNetwork(ServerPlayer player) { + IGrid grid = findPlayerGrid(player); + return PatternTerminalUtil.listAvailableProvidersFromGrid(grid); + } + + private static ItemStack getPendingCtrlQPattern(ServerPlayer player) { + if (player == null) return ItemStack.EMPTY; + String id = player.getPersistentData().getString(PENDING_DATA_KEY); + if (id == null || id.isBlank()) return ItemStack.EMPTY; + + CompoundTag data = player.getPersistentData(); + if (!data.contains(PENDING_STACK_KEY)) return ItemStack.EMPTY; + CompoundTag stackTag = data.getCompound(PENDING_STACK_KEY); + ItemStack stack = ItemStack.of(stackTag); + if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { + clearPendingCtrlQUpload(player); + return ItemStack.EMPTY; + } + return stack; + } + + private static ItemStack insertPatternIntoProviderFromPlayerNetwork(ServerPlayer player, ItemStack pattern, long providerId) { + if (player == null || pattern == null || pattern.isEmpty() || !PatternDetailsHelper.isEncodedPattern(pattern)) { + return pattern == null ? ItemStack.EMPTY : pattern; + } + + int index = decodeProviderIndex(providerId); + if (index < 0) return pattern; + + List providers = listAvailableProvidersFromPlayerNetwork(player); + if (index >= providers.size()) return pattern; + + PatternContainer target = providers.get(index); + if (target == null) return pattern; + + ItemStack remain = pattern.copy(); + for (PatternContainer container : buildSameNameTryList(providers, target)) { + InternalInventory inv = container.getTerminalPatternInventory(); + if (inv == null || inv.size() <= 0) continue; + + ItemStack nextRemain = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()).addItems(remain.copy()); + if (nextRemain.getCount() < remain.getCount()) { + remain = nextRemain; + if (remain.isEmpty()) { + return ItemStack.EMPTY; + } + } + } + return remain; + } + + private static int decodeProviderIndex(long providerId) { + if (providerId >= 0) return -1; + long idx = -1L - providerId; + if (idx > Integer.MAX_VALUE) return -1; + return (int) idx; + } + + private static List buildSameNameTryList(List all, PatternContainer target) { + String targetName = PatternProviderDataUtil.getProviderDisplayName(target); + List tryList = new java.util.ArrayList<>(); + tryList.add(target); + for (PatternContainer container : all) { + if (container == null || container == target) continue; + String name = PatternProviderDataUtil.getProviderDisplayName(container); + if (name != null && name.equals(targetName)) { + tryList.add(container); + } + } + return tryList; + } + + private static IGrid findPlayerGrid(ServerPlayer player) { + WirelessTerminalLocator.LocatedTerminal located = WirelessTerminalLocator.find(player); + ItemStack terminal = located.stack; + if (terminal.isEmpty()) { + return null; + } + + String curiosSlotId = located.getCuriosSlotId(); + int curiosIndex = located.getCuriosIndex(); + + if (curiosSlotId != null && curiosIndex >= 0) { + try { + String current = WUTHandler.getCurrentTerminal(terminal); + WTDefinition def = WUTHandler.wirelessTerminals.get(current); + if (def != null) { + WTMenuHost wtHost = def.wTMenuHostFactory().create(player, null, terminal, (p, sub) -> {}); + if (wtHost != null) { + IGridNode node = wtHost.getActionableNode(); + if (node != null) { + return node.getGrid(); + } + } + } + } catch (Exception ignored) { + return null; + } + } else { + WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null; + if (wt != null) { + return wt.getLinkedGrid(terminal, player.serverLevel(), player); + } + } + + return null; + } + /** * ExtendedAE兼容的样板过滤器 * 使用AE2的PatternDetailsHelper进行样板验证