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 ee6cca0..b2355f4 100644 --- a/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java +++ b/src/main/java/com/extendedae_plus/client/event/CtrlQPatternKeyHandler.java @@ -161,7 +161,110 @@ public class CtrlQPatternKeyHandler { * @param recipeBookmark 配方书签对象 */ private static void handleCraftingRecipeBookmark(Object recipeBookmark) { - System.out.println("hecheng"); + try { + // 1. 获取配方ID + var getRecipeUidMethod = recipeBookmark.getClass().getMethod("getRecipeUid"); + net.minecraft.resources.ResourceLocation recipeId = + (net.minecraft.resources.ResourceLocation) getRecipeUidMethod.invoke(recipeBookmark); + + if (recipeId == null) { + return; + } + + // 2. 获取Minecraft实例 + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) { + return; + } + + // 3. 验证配方存在 + 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; + } + + // 4. 从JEI获取配方的详细信息(输入输出槽位) + // 使用RecipeBookmark的getRecipe方法获取配方对象 + var getRecipeMethod = recipeBookmark.getClass().getMethod("getRecipe"); + Object recipe = getRecipeMethod.invoke(recipeBookmark); + + if (recipe == null) { + return; + } + + // 5. 通过JEI的RecipeManager获取配方布局信息 + mezz.jei.api.runtime.IJeiRuntime jeiRuntime = JeiRuntimeProxy.get(); + if (jeiRuntime == null) { + return; + } + + // 6. 获取配方类别 + var getRecipeCategoryMethod = recipeBookmark.getClass().getMethod("getRecipeCategory"); + Object recipeCategory = getRecipeCategoryMethod.invoke(recipeBookmark); + + // 7. 创建RecipeInfo来应用JEI书签优先级 + 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 mezz.jei.api.ingredients.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; + } + + // 8. 找到匹配的RecipeInfo + RecipeInfo matchingRecipeInfo = null; + for (RecipeInfo info : recipeInfos) { + if (info.getRecipe().getId().equals(recipeId)) { + matchingRecipeInfo = info; + break; + } + } + + if (matchingRecipeInfo == null) { + matchingRecipeInfo = recipeInfos.get(0); + } + + // 9. 应用JEI书签优先级选择材料 + List selectedIngredients = selectIngredientsWithJeiPriority(matchingRecipeInfo); + + // 10. 获取输出材料 + List selectedOutputs = convertOutputsToItemStacks(matchingRecipeInfo); + + // 11. 发送网络包创建样板并上传到装配矩阵 + ModNetwork.CHANNEL.sendToServer(new com.extendedae_plus.network.pattern.CreateAndUploadPatternC2SPacket( + recipeId, + matchingRecipeInfo.isCraftingRecipe(), + selectedIngredients, + selectedOutputs + )); + + } catch (Exception e) { + e.printStackTrace(); + } } /** diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index ebbc3d2..473533c 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -7,6 +7,7 @@ import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket; import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket; import com.extendedae_plus.network.pattern.CreateCtrlQPatternC2SPacket; +import com.extendedae_plus.network.pattern.CreateAndUploadPatternC2SPacket; import com.extendedae_plus.network.provider.*; import com.extendedae_plus.network.upload.EncodeWithShiftFlagC2SPacket; import net.minecraft.resources.ResourceLocation; @@ -175,6 +176,12 @@ public final class ModNetwork { .decoder(CreateCtrlQPatternC2SPacket::decode) .consumerNetworkThread(CreateCtrlQPatternC2SPacket::handle) .add(); + + CHANNEL.messageBuilder(CreateAndUploadPatternC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(CreateAndUploadPatternC2SPacket::encode) + .decoder(CreateAndUploadPatternC2SPacket::decode) + .consumerNetworkThread(CreateAndUploadPatternC2SPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/java/com/extendedae_plus/network/pattern/CreateAndUploadPatternC2SPacket.java b/src/main/java/com/extendedae_plus/network/pattern/CreateAndUploadPatternC2SPacket.java new file mode 100644 index 0000000..d84a44e --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/pattern/CreateAndUploadPatternC2SPacket.java @@ -0,0 +1,297 @@ +package com.extendedae_plus.network.pattern; + +import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.api.networking.energy.IEnergyService; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import appeng.api.storage.StorageHelper; +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.MatrixUploadUtil; +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.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipe; +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: 创建样板并上传到装配矩阵 + * + *

从客户端发送配方ID、选择的材料和输出到服务器,服务器消耗空白样板并创建编码样板,然后直接上传到装配矩阵

+ */ +public class CreateAndUploadPatternC2SPacket { + + private final ResourceLocation recipeId; + private final boolean isCraftingPattern; + private final List selectedIngredients; + private final List outputs; + + public CreateAndUploadPatternC2SPacket(ResourceLocation recipeId, boolean isCraftingPattern, List selectedIngredients, List outputs) { + this.recipeId = recipeId; + this.isCraftingPattern = isCraftingPattern; + this.selectedIngredients = selectedIngredients; + this.outputs = outputs; + } + + public static void encode(CreateAndUploadPatternC2SPacket msg, FriendlyByteBuf buf) { + buf.writeResourceLocation(msg.recipeId); + buf.writeBoolean(msg.isCraftingPattern); + buf.writeInt(msg.selectedIngredients.size()); + for (ItemStack stack : msg.selectedIngredients) { + buf.writeItem(stack); + } + buf.writeInt(msg.outputs.size()); + for (ItemStack stack : msg.outputs) { + buf.writeItem(stack); + } + } + + public static CreateAndUploadPatternC2SPacket 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 CreateAndUploadPatternC2SPacket(recipeId, isCraftingPattern, ingredients, outputs); + } + + public static void handle(CreateAndUploadPatternC2SPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + ServerPlayer player = ctx.getSender(); + if (player == null) { + 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 + ); + return; + } + + Recipe recipe = recipeOpt.get(); + + // 2. 获取AE网络 + IGrid grid = getPlayerGrid(player); + if (grid == null) { + player.displayClientMessage( + Component.translatable("message.extendedae_plus.no_network"), + false + ); + return; + } + + // 3. 消耗空白样板 + if (!consumeBlankPattern(player, grid)) { + player.displayClientMessage( + Component.translatable("message.extendedae_plus.no_blank_pattern"), + false + ); + return; + } + + // 4. 创建样板 + ItemStack pattern = createPattern(recipe, msg.isCraftingPattern, msg.selectedIngredients, msg.outputs, player); + + if (pattern.isEmpty()) { + // 创建失败,退还空白样板到网络 + refundBlankPattern(player, grid); + player.displayClientMessage( + Component.translatable("message.extendedae_plus.pattern_creation_failed"), + false + ); + return; + } + + // 5. 上传样板到装配矩阵 + boolean uploaded = MatrixUploadUtil.uploadPatternToMatrix(player, pattern, grid); + + if (!uploaded) { + // 上传失败,退还空白样板到网络 + refundBlankPattern(player, grid); + } + }); + ctx.setPacketHandled(true); + } + + /** + * 获取玩家的AE网络 + */ + private static IGrid getPlayerGrid(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 e) { + return null; + } + } else { + WirelessTerminalItem wt = terminal.getItem() instanceof WirelessTerminalItem t ? t : null; + if (wt != null) { + return wt.getLinkedGrid(terminal, player.serverLevel(), player); + } + } + + return null; + } + + /** + * 消耗空白样板:优先从AE网络提取 + */ + private static boolean consumeBlankPattern(ServerPlayer player, IGrid grid) { + 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, + new PlayerSource(player) + ); + + return extracted > 0; + } + + /** + * 退还空白样板到网络 + */ + private static void refundBlankPattern(ServerPlayer player, IGrid grid) { + AEItemKey blankPatternKey = AEItemKey.of(AEItems.BLANK_PATTERN.stack()); + IEnergyService energy = grid.getEnergyService(); + MEStorage storage = grid.getStorageService().getInventory(); + + StorageHelper.poweredInsert( + energy, + storage, + blankPatternKey, + 1, + new PlayerSource(player) + ); + } + + /** + * 从配方创建样板 + */ + private static ItemStack createPattern(Recipe recipe, boolean isCrafting, List selectedIngredients, List selectedOutputs, ServerPlayer player) { + try { + if (isCrafting && recipe instanceof CraftingRecipe craftingRecipe) { + // 合成样板 + ItemStack[] inputs = new ItemStack[9]; + for (int i = 0; i < 9; i++) { + if (i < selectedIngredients.size()) { + inputs[i] = selectedIngredients.get(i).copy(); + } else { + inputs[i] = ItemStack.EMPTY; + } + } + + ItemStack output = recipe.getResultItem(player.level().registryAccess()).copy(); + + ItemStack encodedPattern = PatternDetailsHelper.encodeCraftingPattern( + craftingRecipe, + inputs, + output, + true, + false + ); + + 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 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; + } + } +} diff --git a/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java b/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java index dbec59c..8a0fc48 100644 --- a/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java @@ -87,6 +87,61 @@ public final class MatrixUploadUtil { } } } + /** + * 直接上传已创建的样板到装配矩阵(不从菜单读取) + * + * @param player 服务器玩家 + * @param pattern 已编码的样板 + * @param grid AE网络 + * @return 是否上传成功 + */ + public static boolean uploadPatternToMatrix(ServerPlayer player, ItemStack pattern, IGrid grid) { + if (player == null || pattern.isEmpty() || grid == null) { + return false; + } + + // 验证是否为已编码的样板 + if (!PatternDetailsHelper.isEncodedPattern(pattern)) { + return false; + } + + // 仅允许"合成/锻造台/切石机样板" + IPatternDetails details = PatternDetailsHelper.decodePattern(pattern, player.level()); + if (!(details instanceof AECraftingPattern + || details instanceof AESmithingTablePattern + || details instanceof AEStonecuttingPattern)) { + return false; + } + + ItemStack toInsert = pattern.copy(); + + // 收集所有可用的装配矩阵(图样模块)内部库存并逐一尝试(遵循其过滤规则) + List inventories = findAllMatrixPatternInventories(grid); + + // 在尝试上传之前,检查装配矩阵是否已经存在相同样板(物品与NBT完全一致) + if (matrixContainsPattern(inventories, pattern)) { + // 直接提醒并跳过上传 + sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.repetition")); + return false; + } + + // 尝试插入 + for (InternalInventory inv : inventories) { + if (inv == null) continue; + ItemStack remain = inv.addItems(toInsert); + if (remain.getCount() < pattern.getCount()) { + // 上传成功 + sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.success")); + return true; + } + } + + // 所有矩阵都满了 + sendPlayerMessage(player, Component.translatable("extendedae_plus.upload_to_matrix.full")); + return false; + } + + /** * 在给定 AE Grid 中收集所有已成型且在线的装配矩阵“样板核心”的用于外部插入的内部库存