diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/ContainerPatternEncodingTermMenuMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/ContainerPatternEncodingTermMenuMixin.java index 0b09a57..2018545 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/ContainerPatternEncodingTermMenuMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/ContainerPatternEncodingTermMenuMixin.java @@ -4,7 +4,7 @@ import appeng.api.crafting.PatternDetailsHelper; import appeng.menu.me.items.PatternEncodingTermMenu; import appeng.menu.slot.RestrictedInputSlot; import appeng.parts.encoding.EncodingMode; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.uploadPattern.MatrixUploadUtil; import com.glodblock.github.glodium.network.packet.sync.IActionHolder; import com.glodblock.github.glodium.network.packet.sync.Paras; import net.minecraft.server.level.ServerPlayer; @@ -47,7 +47,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo } var stack = this.encodedPatternSlot != null ? this.encodedPatternSlot.getItem() : net.minecraft.world.item.ItemStack.EMPTY; if (stack != null && !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack)) { - ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu); + MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu); } else { // 槽位可能尚未同步到位,继续下一 tick 重试 if (attemptsLeft > 0) { @@ -105,7 +105,7 @@ public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHo // 为避免与 AE2 后续同步竞争,切到下一 tick 执行 sp.server.execute(() -> { try { - ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu); + MatrixUploadUtil.uploadFromEncodingMenuToMatrix(sp, menu); } catch (Throwable ignored) { } }); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2WTlib/ContainerUWirelessExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2WTlib/ContainerUWirelessExPatternTerminalMixin.java index f7607fe..4f5fc34 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2WTlib/ContainerUWirelessExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2WTlib/ContainerUWirelessExPatternTerminalMixin.java @@ -1,6 +1,6 @@ package com.extendedae_plus.mixin.ae2WTlib; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; import com.glodblock.github.extendedae.xmod.wt.ContainerUWirelessExPAT; import com.glodblock.github.extendedae.xmod.wt.HostUWirelessExPAT; import com.glodblock.github.glodium.network.packet.sync.IActionHolder; @@ -43,7 +43,7 @@ public abstract class ContainerUWirelessExPatternTerminalMixin implements IActio int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0)); long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1)); var sp = (ServerPlayer) this.eap$player; - ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); + ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); } catch (Throwable t) { } }); diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternTerminalMixin.java index e824e45..b5ef76a 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternTerminalMixin.java @@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.extendedae.container; import appeng.api.util.IConfigurableObject; import appeng.menu.guisync.GuiSync; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; import com.glodblock.github.extendedae.container.ContainerExPatternTerminal; import com.glodblock.github.glodium.network.packet.sync.IActionHolder; import com.glodblock.github.glodium.network.packet.sync.Paras; @@ -79,7 +79,7 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder { int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0)); long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1)); var sp = (ServerPlayer) this.epp$player; - ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); + ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); } catch (Throwable ignored) { } }); diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerWirelessExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerWirelessExPatternTerminalMixin.java index ce7a1ab..8a8c226 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerWirelessExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerWirelessExPatternTerminalMixin.java @@ -1,6 +1,6 @@ package com.extendedae_plus.mixin.extendedae.container; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; import com.glodblock.github.extendedae.common.me.itemhost.HostWirelessExPAT; import com.glodblock.github.extendedae.container.ContainerWirelessExPAT; import com.glodblock.github.glodium.network.packet.sync.IActionHolder; @@ -43,7 +43,7 @@ public abstract class ContainerWirelessExPatternTerminalMixin implements IAction int playerSlotIndex = (o0 instanceof Number) ? ((Number) o0).intValue() : Integer.parseInt(String.valueOf(o0)); long providerId = (o1 instanceof Number) ? ((Number) o1).longValue() : Long.parseLong(String.valueOf(o1)); var sp = (ServerPlayer) this.epp$player; - ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); + ProviderUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId); } catch (Throwable ignored) { } }); diff --git a/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java b/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java index ddc1470..2f6ccb3 100644 --- a/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java @@ -1,7 +1,7 @@ package com.extendedae_plus.network; import appeng.menu.me.items.PatternEncodingTermMenu; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.uploadPattern.ProviderUploadUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; @@ -36,10 +36,10 @@ public class UploadEncodedPatternToProviderC2SPacket { // 1) providerId >= 0: 访问终端 byId 模式 // 2) providerId < 0: 索引模式(由列表回退路径生成),index = -1 - providerId if (msg.providerId >= 0) { - ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId); + ProviderUploadUtil.uploadFromEncodingMenuToProvider(player, menu, msg.providerId); } else { int index = (int) (-1L - msg.providerId); - ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index); + ProviderUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index); } }); ctx.setPacketHandled(true); 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 919ba09..6fa880c 100644 --- a/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java @@ -4,7 +4,8 @@ import appeng.helpers.patternprovider.PatternContainer; import appeng.menu.implementations.PatternAccessTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu; import com.extendedae_plus.init.ModNetwork; -import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.extendedae_plus.util.PatternProviderDataUtil; +import com.extendedae_plus.util.uploadPattern.PatternTerminalUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; @@ -31,20 +32,20 @@ public class RequestProvidersListC2SPacket { if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return; // 优先:若玩家也打开了样板访问终端,则用 byId 方式(精确服务器ID) - PatternAccessTermMenu accessMenu = ExtendedAEPatternUploadUtil.getPatternAccessMenu(player); + PatternAccessTermMenu accessMenu = PatternTerminalUtil.getPatternAccessMenu(player); if (accessMenu != null) { - List ids = ExtendedAEPatternUploadUtil.getAllProviderIds(accessMenu); + List ids = PatternTerminalUtil.getAllProviderIds(accessMenu); List filteredIds = new ArrayList<>(); List names = new ArrayList<>(); List slots = new ArrayList<>(); for (Long id : ids) { if (id == null) continue; - if (!ExtendedAEPatternUploadUtil.isProviderAvailable(id, accessMenu)) continue; - int empty = ExtendedAEPatternUploadUtil.getAvailableSlots(id, accessMenu); + if (!PatternProviderDataUtil.isProviderAvailable(id, accessMenu)) continue; + int empty = PatternProviderDataUtil.getAvailableSlots(id, accessMenu); if (empty <= 0) continue; // 只列出有空位的 filteredIds.add(id); - names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu)); + names.add(PatternProviderDataUtil.getProviderDisplayName(id, accessMenu)); slots.add(empty); } @@ -53,18 +54,18 @@ public class RequestProvidersListC2SPacket { } // 回退:基于编码终端所在网络枚举供应器,用“负数ID编码索引”:encodedId = -1 - index - List containers = ExtendedAEPatternUploadUtil.listAvailableProvidersFromGrid(encMenu); + List containers = PatternTerminalUtil.listAvailableProvidersFromGrid(encMenu); 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 = ExtendedAEPatternUploadUtil.getAvailableSlots(c); + int empty = PatternProviderDataUtil.getAvailableSlots(c); if (empty <= 0) continue; long encodedId = -1L - i; // 约定:负数代表按索引 idxIds.add(encodedId); - names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c)); + 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); diff --git a/src/main/java/com/extendedae_plus/util/AEMenUtil.java b/src/main/java/com/extendedae_plus/util/AEMenUtil.java new file mode 100644 index 0000000..63402f2 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/AEMenUtil.java @@ -0,0 +1,4 @@ +package com.extendedae_plus.util; + +public class AEMenUtil { +} diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java deleted file mode 100644 index 9cbb9be..0000000 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ /dev/null @@ -1,755 +0,0 @@ -package com.extendedae_plus.util; - -import appeng.api.crafting.IPatternDetails; -import appeng.api.crafting.PatternDetailsHelper; -import appeng.api.inventories.InternalInventory; -import appeng.api.networking.IGrid; -import appeng.api.networking.IGridNode; -import appeng.core.definitions.AEItems; -import appeng.crafting.pattern.AECraftingPattern; -import appeng.crafting.pattern.AESmithingTablePattern; -import appeng.crafting.pattern.AEStonecuttingPattern; -import appeng.helpers.patternprovider.PatternContainer; -import appeng.menu.implementations.PatternAccessTermMenu; -import appeng.menu.me.items.PatternEncodingTermMenu; -import appeng.util.inv.FilteredInternalInventory; -import appeng.util.inv.filter.IAEItemFilter; -import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor; -import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.item.ItemStack; -import net.minecraftforge.common.capabilities.ForgeCapabilities; -import net.minecraftforge.items.IItemHandler; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * ExtendedAE扩展样板管理终端专用的样板上传工具类 - * 兼容ExtendedAE的ContainerExPatternTerminal和原版AE2的PatternAccessTermMenu - */ -public class ExtendedAEPatternUploadUtil { - /** - * 获取玩家当前的样板访问终端菜单(支持ExtendedAE和原版AE2) - * - * @param player 玩家 - * @return PatternAccessTermMenu实例,如果玩家没有打开则返回null - */ - public static PatternAccessTermMenu getPatternAccessMenu(ServerPlayer player) { - if (player == null || player.containerMenu == null) { - return null; - } - // 优先检查ExtendedAE的扩展样板管理终端(使用类名检查避免直接导入) - String containerClassName = player.containerMenu.getClass().getName(); - if (containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal")) { - // ExtendedAE的容器继承自PatternAccessTermMenu,可以安全转换 - return (PatternAccessTermMenu) player.containerMenu; - } - // 兼容原版AE2的样板访问终端 - if (player.containerMenu instanceof PatternAccessTermMenu) { - return (PatternAccessTermMenu) player.containerMenu; - } - return null; - } - - /** - * 从 AE2 的图样编码终端菜单上传当前“已编码图样”至 ExtendedAE 装配矩阵(仅合成图样)。 - * 不会处理“处理图样”。 - * - * @param player 服务器玩家 - * @param menu PatternEncodingTermMenu - * @return 是否成功插入矩阵 - */ - public static boolean uploadFromEncodingMenuToMatrix(ServerPlayer player, PatternEncodingTermMenu menu) { - if (player == null || menu == null) { - return false; - } - - // 读取已编码槽位的物品 - var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) - .eap$getEncodedPatternSlot(); - ItemStack stack = encodedSlot.getItem(); - if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { - sendMessage(player, "ExtendedAE Plus: 没有可上传的编码样板"); - return false; - } - - // 仅允许“合成/锻造台/切石机图样” - IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level()); - if (!(details instanceof AECraftingPattern - || details instanceof AESmithingTablePattern - || details instanceof AEStonecuttingPattern)) { - sendMessage(player, "extendedae_plus.upload_to_matrix.fail"); - return false; - } - - // 获取 AE 网络 - IGridNode node = menu.getNetworkNode(); - if (node == null) { - sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中"); - return false; - } - IGrid grid = node.getGrid(); - if (grid == null) { - sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中"); - return false; - } - - // 在尝试上传之前,检查装配矩阵是否已经存在相同样板(物品与NBT完全一致) - if (matrixContainsPattern(grid, stack)) { - // 直接提醒并跳过上传,并将同等数量的空白样板放回空白样板槽,否则退回玩家背包 - if (player != null) { - player.sendSystemMessage(Component.literal("ExtendedAE Plus: 装配矩阵已存在相同样板,已跳过上传并返还空白样板")); - } - try { - var accessor = (PatternEncodingTermMenuAccessor) (Object) menu; - var blankSlot = accessor.eap$getBlankPatternSlot(); - ItemStack blanks = AEItems.BLANK_PATTERN.stack(stack.getCount()); - if (blankSlot != null && blankSlot.mayPlace(blanks)) { - ItemStack remain = blankSlot.safeInsert(blanks); - if (!remain.isEmpty() && player != null) { - player.getInventory().placeItemBackInInventory(remain, false); - } - } else if (player != null) { - player.getInventory().placeItemBackInInventory(blanks, false); - } - } catch (Throwable t) { - if (player != null) { - // 兜底:直接还给玩家背包 - player.getInventory().placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(stack.getCount()), false); - } - } - // 清空编码样板槽,防止再次输出 - encodedSlot.set(ItemStack.EMPTY); - return false; - } - - // 收集所有可用的装配矩阵(图样模块)内部库存并逐一尝试(遵循其过滤规则) - List inventories = findAllMatrixPatternInventories(grid); - if (!inventories.isEmpty()) { - for (int i = 0; i < inventories.size(); i++) { - var inv = inventories.get(i); - ItemStack toInsert = stack.copy(); - ItemStack remain = inv.addItems(toInsert); - if (remain.getCount() < stack.getCount()) { - int inserted = stack.getCount() - remain.getCount(); - stack.shrink(inserted); - if (stack.isEmpty()) { - encodedSlot.set(ItemStack.EMPTY); - } - sendMessage(player, "extendedae_plus.upload_to_matrix.success"); - return true; - } - } - // 所有内部库存都无法接收 -> 尝试 capability 回退 - } - - // 回退:尝试 Forge 能力(可能为聚合图样仓),同样遍历所有矩阵 - List handlers = findAllMatrixPatternHandlers(grid); - if (!handlers.isEmpty()) { - for (int i = 0; i < handlers.size(); i++) { - var cap = handlers.get(i); - ItemStack toInsert = stack.copy(); - ItemStack remain = insertIntoAnySlot(cap, toInsert); - if (remain.getCount() < stack.getCount()) { - int inserted = stack.getCount() - remain.getCount(); - stack.shrink(inserted); - if (stack.isEmpty()) { - encodedSlot.set(ItemStack.EMPTY); - } - sendMessage(player, "extendedae_plus.upload_to_matrix.success"); - return true; - } - } - } - - // 未找到可用矩阵或全部拒收 - if (inventories.isEmpty() && handlers.isEmpty()) { - sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix"); - } else { - sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full"); - } - return false; - } - - /** - * 在给定 AE Grid 中收集所有已成型且在线的装配矩阵“图样模块”的用于外部插入的内部库存。 - * 优先使用 TileAssemblerMatrixPattern#getExposedInventory(仅允许插入,且已带AE过滤规则)。 - */ - private static List findAllMatrixPatternInventories(IGrid grid) { - List result = new ArrayList<>(); - try { - var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class); - int idx = 0; - for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) { - if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) { - var inv = tile.getExposedInventory(); - if (inv != null) { - result.add(inv); - } - } - idx++; - } - } catch (Throwable t) { - } - return result; - } - - /** - * 在给定 AE Grid 中收集所有已成型的装配矩阵的聚合图样仓 IItemHandler(若可用)。 - */ - private static List findAllMatrixPatternHandlers(IGrid grid) { - List result = new ArrayList<>(); - try { - Set matrices = grid.getMachines(TileAssemblerMatrixBase.class); - int idx = 0; - for (TileAssemblerMatrixBase tile : matrices) { - if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) { - var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null); - if (capOpt != null) { - var handler = capOpt.orElse(null); - if (handler != null) { - result.add(handler); - } - } - } - idx++; - } - } catch (Throwable ignored) { - } - return result; - } - - /** - * 尝试将整个物品栈插入到 IItemHandler 的任意槽位,返回剩余物品。 - */ - private static ItemStack insertIntoAnySlot(IItemHandler handler, ItemStack stack) { - ItemStack remaining = stack.copy(); - if (handler == null || remaining.isEmpty()) return remaining; - for (int i = 0; i < handler.getSlots(); i++) { - remaining = handler.insertItem(i, remaining, false); - if (remaining.isEmpty()) break; - } - return remaining; - } - - /** - * 检查装配矩阵(所有已成型矩阵的图样仓)中是否已存在与给定样板完全相同的物品(含NBT)。 - */ - private static boolean matrixContainsPattern(IGrid grid, ItemStack pattern) { - if (grid == null || pattern == null || pattern.isEmpty()) return false; - try { - // 先检查提供外部插入视图的内部库存 - List inventories = findAllMatrixPatternInventories(grid); - for (InternalInventory inv : inventories) { - if (inv == null) continue; - for (int i = 0; i < inv.size(); i++) { - ItemStack s = inv.getStackInSlot(i); - if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) { - return true; - } - } - } - } catch (Throwable t) { - } - try { - // 再检查聚合能力视图 - List handlers = findAllMatrixPatternHandlers(grid); - for (IItemHandler h : handlers) { - if (h == null) continue; - int slots = h.getSlots(); - for (int i = 0; i < slots; i++) { - ItemStack s = h.getStackInSlot(i); - if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) { - return true; - } - } - } - } catch (Throwable t) { - } - return false; - } - - /** - * 判断给定矩阵集群中是否存在“装配矩阵上传核心”。 - * 要求:至少存在 1 个即可,不限制数量。 - * 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。 - */ - private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) { - try { - if (any == null || any.getCluster() == null) return false; - int cores = 0; - var it = any.getCluster().getBlockEntities(); - while (it.hasNext()) { - var te = it.next(); - if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) { - cores++; - } - } - return cores >= 1; // 至少一个即可 - } catch (Throwable t) { - return false; - } - } - - /** - * 检查当前菜单是否为ExtendedAE的扩展样板管理终端 - * - * @param player 玩家 - * @return 是否为ExtendedAE扩展终端 - */ - public static boolean isExtendedAETerminal(ServerPlayer player) { - if (player == null || player.containerMenu == null) { - return false; - } - - String containerClassName = player.containerMenu.getClass().getName(); - return containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal"); - } - - /** - * 将玩家背包中的样板上传到指定的样板供应器 - * 兼容ExtendedAE和原版AE2 - * - * @param player 玩家 - * @param playerSlotIndex 玩家背包槽位索引 - * @param providerId 目标样板供应器的服务器ID - * @return 是否上传成功 - */ - public static boolean uploadPatternToProvider(ServerPlayer player, int playerSlotIndex, long providerId) { - // 1. 验证玩家是否打开了样板访问终端 - PatternAccessTermMenu menu = getPatternAccessMenu(player); - if (menu == null) { - sendMessage(player, "ExtendedAE Plus: 请先打开样板访问终端或扩展样板管理终端"); - return false; - } - - // 2. 获取玩家背包中的物品 - ItemStack playerItem = player.getInventory().getItem(playerSlotIndex); - if (playerItem.isEmpty()) { - sendMessage(player, "ExtendedAE Plus: 背包槽位为空"); - return false; - } - - // 3. 验证是否是编码样板 - if (!PatternDetailsHelper.isEncodedPattern(playerItem)) { - sendMessage(player, "ExtendedAE Plus: 该物品不是有效的编码样板"); - return false; - } - - // 4. 获取目标样板供应器 - PatternContainer patternContainer = getPatternContainerById(menu, providerId); - if (patternContainer == null) { - sendMessage(player, "ExtendedAE Plus: 找不到指定的样板供应器 (ID: " + providerId + ")"); - return false; - } - - // 5. 获取样板供应器的库存 - InternalInventory patternInventory = patternContainer.getTerminalPatternInventory(); - if (patternInventory == null) { - sendMessage(player, "ExtendedAE Plus: 无法访问样板供应器的库存"); - return false; - } - - // 6. 使用AE2的标准样板过滤器进行插入 - var patternFilter = new ExtendedAEPatternFilter(); - var filteredInventory = new FilteredInternalInventory(patternInventory, patternFilter); - - // 7. 尝试插入样板 - ItemStack itemToInsert = playerItem.copy(); - ItemStack remaining = filteredInventory.addItems(itemToInsert); - - if (remaining.getCount() < itemToInsert.getCount()) { - // 插入成功(部分或全部) - int insertedCount = itemToInsert.getCount() - remaining.getCount(); - playerItem.shrink(insertedCount); - - if (playerItem.isEmpty()) { - player.getInventory().setItem(playerSlotIndex, ItemStack.EMPTY); - } - - String terminalType = isExtendedAETerminal(player) ? "扩展样板管理终端" : "样板访问终端"; - sendMessage(player, "ExtendedAE Plus: 通过" + terminalType + "成功上传 " + insertedCount + " 个样板"); - return true; - } else { - sendMessage(player, "ExtendedAE Plus: 上传失败 - 样板供应器已满或样板无效"); - return false; - } - } - - /** - * 获取样板供应器中的空槽位数量 - * - * @param providerId 供应器ID - * @param menu 样板访问终端菜单(支持ExtendedAE) - * @return 空槽位数量,如果无法访问则返回-1 - */ - public static int getAvailableSlots(long providerId, PatternAccessTermMenu menu) { - PatternContainer container = getPatternContainerById(menu, providerId); - if (container == null) { - return -1; - } - - InternalInventory inventory = container.getTerminalPatternInventory(); - if (inventory == null) { - return -1; - } - - int availableSlots = 0; - for (int i = 0; i < inventory.size(); i++) { - if (inventory.getStackInSlot(i).isEmpty()) { - availableSlots++; - } - } - - return availableSlots; - } - - /** - * 通过服务器ID获取PatternContainer - * 兼容ExtendedAE的ContainerExPatternTerminal和原版PatternAccessTermMenu - * - * @param menu 样板访问终端菜单 - * @param providerId 供应器服务器ID - * @return PatternContainer实例,如果不存在则返回null - */ - private static PatternContainer getPatternContainerById(PatternAccessTermMenu menu, long providerId) { - try { - // 通过反射访问byId字段(ExtendedAE继承了这个字段) - Field byIdField = findByIdField(menu.getClass()); - if (byIdField == null) { - System.err.println("ExtendedAE Plus: 无法找到byId字段"); - return null; - } - - byIdField.setAccessible(true); - - @SuppressWarnings("unchecked") - Map byId = (Map) byIdField.get(menu); - - Object containerTracker = byId.get(providerId); - if (containerTracker == null) { - return null; - } - - // 从ContainerTracker中获取PatternContainer - Field containerField = findContainerField(containerTracker.getClass()); - if (containerField == null) { - System.err.println("ExtendedAE Plus: 无法找到container字段"); - return null; - } - - containerField.setAccessible(true); - return (PatternContainer) containerField.get(containerTracker); - - } catch (Exception e) { - System.err.println("ExtendedAE Plus: 无法获取PatternContainer,错误: " + e.getMessage()); - return null; - } - } - - /** - * 在类层次结构中查找byId字段 - */ - private static Field findByIdField(Class clazz) { - Class currentClass = clazz; - while (currentClass != null) { - try { - return currentClass.getDeclaredField("byId"); - } catch (NoSuchFieldException e) { - currentClass = currentClass.getSuperclass(); - } - } - return null; - } - - /** - * 在类层次结构中查找container字段 - */ - private static Field findContainerField(Class clazz) { - Class currentClass = clazz; - while (currentClass != null) { - try { - return currentClass.getDeclaredField("container"); - } catch (NoSuchFieldException e) { - currentClass = currentClass.getSuperclass(); - } - } - return null; - } - - /** - * 发送消息给玩家 - * - * @param player 玩家 - * @param message 消息内容 - */ - private static void sendMessage(ServerPlayer player, String message) { - // 静默:不再向玩家左下角发送任何提示信息 - // 如需恢复,取消下面注释即可: - // if (player != null) { - // player.sendSystemMessage(Component.literal(message)); - // } - // 如果玩家为null,静默忽略(用于测试环境) - } - - /** - * ExtendedAE兼容的样板过滤器 - * 使用AE2的PatternDetailsHelper进行样板验证 - */ - private static class ExtendedAEPatternFilter implements IAEItemFilter { - @Override - public boolean allowExtract(InternalInventory inv, int slot, int amount) { - return true; - } - - @Override - public boolean allowInsert(InternalInventory inv, int slot, ItemStack stack) { - return !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack); - } - } - - /** - * 获取样板供应器的显示名称 - * - * @param providerId 供应器ID - * @param menu 样板访问终端菜单 - * @return 显示名称,如果无法获取则返回"未知供应器" - */ - public static String getProviderDisplayName(long providerId, PatternAccessTermMenu menu) { - PatternContainer container = getPatternContainerById(menu, providerId); - if (container == null) { - return "未知供应器"; - } - - try { - // 尝试获取供应器的组信息来构建显示名称 - var group = container.getTerminalGroup(); - if (group != null) { - // 使用 Component 序列化来保持翻译键,而不是直接 getString() - // 这样客户端可以根据自己的语言设置进行翻译 - return Component.Serializer.toJson(group.name()); - } - } catch (Exception e) { - // 忽略异常,使用默认名称 - } - - return "样板供应器 #" + providerId; - } - - /** - * 验证样板供应器是否可用 - * - * @param providerId 供应器ID - * @param menu 样板访问终端菜单 - * @return 是否可用 - */ - public static boolean isProviderAvailable(long providerId, PatternAccessTermMenu menu) { - PatternContainer container = getPatternContainerById(menu, providerId); - if (container == null) { - return false; - } - - // 检查是否在终端中可见 - if (!container.isVisibleInTerminal()) { - return false; - } - - // 检查是否连接到网络 - return container.getGrid() != null; - } - - /** - * 将图样编码终端的“已编码图样”上传到指定的样板供应器(通过 providerId 定位)。 - */ - public static boolean uploadFromEncodingMenuToProvider(ServerPlayer player, PatternEncodingTermMenu menu, long providerId) { - if (player == null || menu == null) { - return false; - } - var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) - .eap$getEncodedPatternSlot(); - ItemStack stack = encodedSlot.getItem(); - if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { - return false; - } - - PatternAccessTermMenu accessMenu = getPatternAccessMenu(player); - if (accessMenu == null) { - return false; - } - // 先确定目标容器名称,用于同名回退 - String targetName = getProviderDisplayName(providerId, accessMenu); - // 构建尝试顺序:先指定ID,其次同名的其他ID - java.util.List tryIds = new java.util.ArrayList<>(); - tryIds.add(providerId); - try { - java.util.List all = getAllProviderIds(accessMenu); - for (Long id : all) { - if (id == null || id == providerId) continue; - String name = getProviderDisplayName(id, accessMenu); - if (name != null && name.equals(targetName)) { - tryIds.add(id); - } - } - } catch (Throwable ignored) {} - - // 按顺序逐个尝试插入 - for (Long id : tryIds) { - PatternContainer c = getPatternContainerById(accessMenu, id); - if (c == null || !c.isVisibleInTerminal()) continue; - InternalInventory inv = c.getTerminalPatternInventory(); - if (inv == null || inv.size() <= 0) continue; - - var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()); - ItemStack toInsert = stack.copy(); - ItemStack remain = filtered.addItems(toInsert); - if (remain.getCount() < toInsert.getCount()) { - int inserted = toInsert.getCount() - remain.getCount(); - stack.shrink(inserted); - if (stack.isEmpty()) { - encodedSlot.set(ItemStack.EMPTY); - } else { - encodedSlot.set(stack); - } - return true; - } - } - return false; - } - - /** - * 列出当前菜单中所有供应器的服务器ID(原样返回 byId 的 key 集合)。 - */ - public static java.util.List getAllProviderIds(PatternAccessTermMenu menu) { - java.util.List result = new java.util.ArrayList<>(); - if (menu == null) return result; - try { - java.lang.reflect.Field byIdField = findByIdField(menu.getClass()); - if (byIdField == null) return result; - byIdField.setAccessible(true); - @SuppressWarnings("unchecked") - java.util.Map byId = (java.util.Map) byIdField.get(menu); - if (byId != null) { - result.addAll(byId.keySet()); - } - } catch (Throwable ignored) { - } - return result; - } - - /** - * 基于编码终端菜单的 AE Grid 遍历,列出“可在终端中可见且有空位”的供应器容器。 - * 返回顺序稳定:按 grid 的 machineClasses 顺序,再按 activeMachines 迭代顺序。 - */ - public static List listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) { - List list = new ArrayList<>(); - if (menu == null) return list; - try { - IGridNode node = menu.getNetworkNode(); - if (node == null) return list; - IGrid grid = node.getGrid(); - if (grid == null) return list; - for (var machineClass : grid.getMachineClasses()) { - if (PatternContainer.class.isAssignableFrom(machineClass)) { - @SuppressWarnings("unchecked") - Class containerClass = (Class) machineClass; - for (var container : grid.getActiveMachines(containerClass)) { - if (container == null || !container.isVisibleInTerminal()) continue; - InternalInventory inv = container.getTerminalPatternInventory(); - 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 (hasEmpty) list.add(container); - } - } - } - } catch (Throwable ignored) { - } - return list; - } - - /** 获取供应器显示名(优先组名) */ - public static String getProviderDisplayName(PatternContainer container) { - if (container == null) return "未知供应器"; - try { - var group = container.getTerminalGroup(); - if (group != null) { - // 使用 Component 序列化来保持翻译键,而不是直接 getString() - // 这样客户端可以根据自己的语言设置进行翻译 - return Component.Serializer.toJson(group.name()); - } - } catch (Throwable ignored) { - } - return "样板供应器"; - } - - /** 计算供应器空槽位数量 */ - public static int getAvailableSlots(PatternContainer container) { - if (container == null) return -1; - InternalInventory inv = container.getTerminalPatternInventory(); - if (inv == null) return -1; - int available = 0; - for (int i = 0; i < inv.size(); i++) { - if (inv.getStackInSlot(i).isEmpty()) available++; - } - return available; - } - - /** - * 基于“索引”的定向上传:使用 listAvailableProvidersFromGrid(menu) 的顺序, - * 将编码槽样板插入到第 index 个供应器。 - */ - public static boolean uploadFromEncodingMenuToProviderByIndex(ServerPlayer player, PatternEncodingTermMenu menu, int index) { - if (player == null || menu == null || index < 0) return false; - List list = listAvailableProvidersFromGrid(menu); - if (index >= list.size()) return false; - var container = list.get(index); - if (container == null) return false; - - var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) - .eap$getEncodedPatternSlot(); - ItemStack stack = encodedSlot.getItem(); - if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { - return false; - } - - // 以名称为键,同名供应器依次尝试:先 index 指定的,再同名的其他 - String targetName = getProviderDisplayName(container); - java.util.List tryList = new java.util.ArrayList<>(); - tryList.add(container); - try { - for (PatternContainer c : list) { - if (c == null || c == container) continue; - String name = getProviderDisplayName(c); - if (name != null && name.equals(targetName)) { - tryList.add(c); - } - } - } catch (Throwable ignored) {} - - for (PatternContainer c : tryList) { - InternalInventory inv = c.getTerminalPatternInventory(); - if (inv == null || inv.size() <= 0) continue; - var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()); - ItemStack toInsert = stack.copy(); - ItemStack remain = filtered.addItems(toInsert); - if (remain.getCount() < toInsert.getCount()) { - int inserted = toInsert.getCount() - remain.getCount(); - stack.shrink(inserted); - if (stack.isEmpty()) { - encodedSlot.set(ItemStack.EMPTY); - } else { - encodedSlot.set(stack); - } - return true; - } - } - return false; - } -} diff --git a/src/main/java/com/extendedae_plus/util/PatternProviderDataUtil.java b/src/main/java/com/extendedae_plus/util/PatternProviderDataUtil.java index bd0e063..559707b 100644 --- a/src/main/java/com/extendedae_plus/util/PatternProviderDataUtil.java +++ b/src/main/java/com/extendedae_plus/util/PatternProviderDataUtil.java @@ -4,8 +4,12 @@ import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.PatternDetailsHelper; import appeng.api.inventories.InternalInventory; import appeng.api.networking.IGrid; +import appeng.helpers.patternprovider.PatternContainer; import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.menu.implementations.PatternAccessTermMenu; import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; +import com.extendedae_plus.util.uploadPattern.PatternTerminalUtil; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; @@ -113,4 +117,112 @@ public class PatternProviderDataUtil { } return null; } + + + /** + * 获取样板供应器中的空槽位数量 + * + * @param providerId 供应器ID + * @param menu 样板访问终端菜单(支持ExtendedAE) + * @return 空槽位数量,如果无法访问则返回-1 + */ + public static int getAvailableSlots(long providerId, PatternAccessTermMenu menu) { + PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId); + if (container == null) { + return -1; + } + + InternalInventory inventory = container.getTerminalPatternInventory(); + if (inventory == null) { + return -1; + } + + int availableSlots = 0; + for (int i = 0; i < inventory.size(); i++) { + if (inventory.getStackInSlot(i).isEmpty()) { + availableSlots++; + } + } + + return availableSlots; + } + + /** + * 获取样板供应器的显示名称 + * + * @param providerId 供应器ID + * @param menu 样板访问终端菜单 + * @return 显示名称,如果无法获取则返回"未知供应器" + */ + public static String getProviderDisplayName(long providerId, PatternAccessTermMenu menu) { + PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId); + if (container == null) { + return "未知供应器"; + } + + try { + // 尝试获取供应器的组信息来构建显示名称 + var group = container.getTerminalGroup(); + if (group != null) { + // 使用 Component 序列化来保持翻译键,而不是直接 getString() + // 这样客户端可以根据自己的语言设置进行翻译 + return Component.Serializer.toJson(group.name()); + } + } catch (Exception e) { + // 忽略异常,使用默认名称 + } + + return "样板供应器 #" + providerId; + } + + /** + * 验证样板供应器是否可用 + * + * @param providerId 供应器ID + * @param menu 样板访问终端菜单 + * @return 是否可用 + */ + public static boolean isProviderAvailable(long providerId, PatternAccessTermMenu menu) { + PatternContainer container = PatternTerminalUtil.getPatternContainerById(menu, providerId); + if (container == null) { + return false; + } + + // 检查是否在终端中可见 + if (!container.isVisibleInTerminal()) { + return false; + } + + // 检查是否连接到网络 + return container.getGrid() != null; + } + + /** 获取供应器显示名(优先组名) */ + public static String getProviderDisplayName(PatternContainer container) { + if (container == null) return "未知供应器"; + try { + var group = container.getTerminalGroup(); + if (group != null) { + // 使用 Component 序列化来保持翻译键,而不是直接 getString() + // 这样客户端可以根据自己的语言设置进行翻译 + return Component.Serializer.toJson(group.name()); + } + } catch (Throwable ignored) { + } + return "样板供应器"; + } + + /** 计算供应器空槽位数量 */ + public static int getAvailableSlots(PatternContainer container) { + if (container == null) return -1; + InternalInventory inv = container.getTerminalPatternInventory(); + if (inv == null) return -1; + int available = 0; + for (int i = 0; i < inv.size(); i++) { + if (inv.getStackInSlot(i).isEmpty()) available++; + } + return available; + } + + } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java b/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java new file mode 100644 index 0000000..f41cd86 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/uploadPattern/MatrixUploadUtil.java @@ -0,0 +1,279 @@ +package com.extendedae_plus.util.uploadPattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.inventories.InternalInventory; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.core.definitions.AEItems; +import appeng.crafting.pattern.AECraftingPattern; +import appeng.crafting.pattern.AESmithingTablePattern; +import appeng.crafting.pattern.AEStonecuttingPattern; +import appeng.menu.me.items.PatternEncodingTermMenu; +import appeng.menu.slot.RestrictedInputSlot; +import com.extendedae_plus.content.matrix.UploadCoreBlockEntity; +import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor; +import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase; +import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.items.IItemHandler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * 与装配矩阵 (assembler matrix) 上传 / 检查 相关的工具方法。 + * 保留原有逻辑与容错行为。 + */ +public final class MatrixUploadUtil { + private MatrixUploadUtil() {} + /** + * 发送消息给玩家 + * + * @param player 玩家 + * @param message 消息内容 + */ + private static void sendMessage(ServerPlayer player, String message) { + // 静默:不再向玩家左下角发送任何提示信息 + // 如需恢复,取消下面注释即可: + // if (player != null) { + // player.sendSystemMessage(Component.literal(message)); + // } + // 如果玩家为null,静默忽略(用于测试环境) + } + + /** + * 从 AE2 的图样编码终端菜单上传当前“已编码图样”至 ExtendedAE 装配矩阵(仅合成图样)。 + * 不会处理“处理图样”。 + * + * @param player 服务器玩家 + * @param menu PatternEncodingTermMenu + * @return 是否成功插入矩阵 + */ + public static boolean uploadFromEncodingMenuToMatrix(ServerPlayer player, PatternEncodingTermMenu menu) { + if (player == null || menu == null) { + return false; + } + + // 读取已编码槽位的物品 + RestrictedInputSlot encodedSlot = ((PatternEncodingTermMenuAccessor)menu).eap$getEncodedPatternSlot(); + ItemStack stack = encodedSlot.getItem(); + if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { + return false; + } + + // 仅允许“合成/锻造台/切石机图样” + IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level()); + if (!(details instanceof AECraftingPattern + || details instanceof AESmithingTablePattern + || details instanceof AEStonecuttingPattern)) { + return false; + } + + // 获取 AE 网络 + IGridNode node = menu.getNetworkNode(); + if (node == null) { + return false; + } + IGrid grid = node.getGrid(); + if (grid == null) { + return false; + } + + // 在尝试上传之前,检查装配矩阵是否已经存在相同样板(物品与NBT完全一致) + if (matrixContainsPattern(grid, stack)) { + // 直接提醒并跳过上传,并将同等数量的空白样板放回空白样板槽,否则退回玩家背包 + if (player != null) { + player.sendSystemMessage(Component.literal("ExtendedAE Plus: 装配矩阵已存在相同样板,已跳过上传并返还空白样板")); + } + try { + var accessor = (PatternEncodingTermMenuAccessor) (Object) menu; + var blankSlot = accessor.eap$getBlankPatternSlot(); + ItemStack blanks = AEItems.BLANK_PATTERN.stack(stack.getCount()); + if (blankSlot != null && blankSlot.mayPlace(blanks)) { + ItemStack remain = blankSlot.safeInsert(blanks); + if (!remain.isEmpty() && player != null) { + player.getInventory().placeItemBackInInventory(remain, false); + } + } else if (player != null) { + player.getInventory().placeItemBackInInventory(blanks, false); + } + } catch (Throwable t) { + if (player != null) { + // 兜底:直接还给玩家背包 + player.getInventory().placeItemBackInInventory(AEItems.BLANK_PATTERN.stack(stack.getCount()), false); + } + } + // 清空编码样板槽,防止再次输出 + encodedSlot.set(ItemStack.EMPTY); + return false; + } + + // 收集所有可用的装配矩阵(图样模块)内部库存并逐一尝试(遵循其过滤规则) + List inventories = findAllMatrixPatternInventories(grid); + if (!inventories.isEmpty()) { + for (int i = 0; i < inventories.size(); i++) { + var inv = inventories.get(i); + ItemStack toInsert = stack.copy(); + ItemStack remain = inv.addItems(toInsert); + if (remain.getCount() < stack.getCount()) { + int inserted = stack.getCount() - remain.getCount(); + stack.shrink(inserted); + if (stack.isEmpty()) { + encodedSlot.set(ItemStack.EMPTY); + } + sendMessage(player, "extendedae_plus.upload_to_matrix.success"); + return true; + } + } + // 所有内部库存都无法接收 -> 尝试 capability 回退 + } + + // 回退:尝试 Forge 能力(可能为聚合图样仓),同样遍历所有矩阵 + List handlers = findAllMatrixPatternHandlers(grid); + if (!handlers.isEmpty()) { + for (int i = 0; i < handlers.size(); i++) { + var cap = handlers.get(i); + ItemStack toInsert = stack.copy(); + ItemStack remain = insertIntoAnySlot(cap, toInsert); + if (remain.getCount() < stack.getCount()) { + int inserted = stack.getCount() - remain.getCount(); + stack.shrink(inserted); + if (stack.isEmpty()) { + encodedSlot.set(ItemStack.EMPTY); + } + sendMessage(player, "extendedae_plus.upload_to_matrix.success"); + return true; + } + } + } + + // 未找到可用矩阵或全部拒收 + if (inventories.isEmpty() && handlers.isEmpty()) { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix"); + } else { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full"); + } + return false; + } + + /** + * 在给定 AE Grid 中收集所有已成型且在线的装配矩阵“图样模块”的用于外部插入的内部库存。 + * 优先使用 TileAssemblerMatrixPattern#getExposedInventory(仅允许插入,且已带AE过滤规则)。 + */ + private static List findAllMatrixPatternInventories(IGrid grid) { + List result = new ArrayList<>(); + try { + var tiles = grid.getMachines(TileAssemblerMatrixPattern.class); + for (TileAssemblerMatrixPattern tile : tiles) { + if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) { + var inv = tile.getExposedInventory(); + if (inv != null) { + result.add(inv); + } + } + } + } catch (Throwable t) { + } + return result; + } + + /** + * 在给定 AE Grid 中收集所有已成型的装配矩阵的聚合图样仓 IItemHandler(若可用)。 + */ + private static List findAllMatrixPatternHandlers(IGrid grid) { + List result = new ArrayList<>(); + try { + Set matrices = grid.getMachines(TileAssemblerMatrixBase.class); + for (TileAssemblerMatrixBase tile : matrices) { + if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) { + var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null); + if (capOpt != null) { + var handler = capOpt.orElse(null); + if (handler != null) { + result.add(handler); + } + } + } + } + } catch (Throwable ignored) { + } + return result; + } + + /** + * 检查装配矩阵(所有已成型矩阵的图样仓)中是否已存在与给定样板完全相同的物品(含NBT)。 + */ + private static boolean matrixContainsPattern(IGrid grid, ItemStack pattern) { + if (grid == null || pattern == null || pattern.isEmpty()) return false; + try { + // 先检查提供外部插入视图的内部库存 + List inventories = findAllMatrixPatternInventories(grid); + for (InternalInventory inv : inventories) { + if (inv == null) continue; + for (int i = 0; i < inv.size(); i++) { + ItemStack s = inv.getStackInSlot(i); + if (!s.isEmpty() && ItemStack.isSameItemSameTags(s, pattern)) { + return true; + } + } + } + } catch (Throwable t) { + } + try { + // 再检查聚合能力视图 + List handlers = findAllMatrixPatternHandlers(grid); + for (IItemHandler h : handlers) { + if (h == null) continue; + int slots = h.getSlots(); + for (int i = 0; i < slots; i++) { + ItemStack s = h.getStackInSlot(i); + if (!s.isEmpty() && ItemStack.isSameItemSameTags(s, pattern)) { + return true; + } + } + } + } catch (Throwable t) { + } + return false; + } + + /** + * 判断给定矩阵集群中是否存在“装配矩阵上传核心”。 + * 要求:至少存在 1 个即可,不限制数量。 + * 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。 + */ + private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) { + try { + if (any == null || any.getCluster() == null) return false; + int cores = 0; + var it = any.getCluster().getBlockEntities(); + while (it.hasNext()) { + var te = it.next(); + if (te instanceof UploadCoreBlockEntity) { + cores++; + } + } + return cores >= 1; // 至少一个即可 + } catch (Throwable t) { + return false; + } + } + + /** + * 尝试将整个物品栈插入到 IItemHandler 的任意槽位,返回剩余物品。 + */ + private static ItemStack insertIntoAnySlot(IItemHandler handler, ItemStack stack) { + ItemStack remaining = stack.copy(); + if (handler == null || remaining.isEmpty()) return remaining; + for (int i = 0; i < handler.getSlots(); i++) { + remaining = handler.insertItem(i, remaining, false); + if (remaining.isEmpty()) break; + } + return remaining; + } +} diff --git a/src/main/java/com/extendedae_plus/util/uploadPattern/PatternTerminalUtil.java b/src/main/java/com/extendedae_plus/util/uploadPattern/PatternTerminalUtil.java new file mode 100644 index 0000000..de00cf7 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/uploadPattern/PatternTerminalUtil.java @@ -0,0 +1,189 @@ +package com.extendedae_plus.util.uploadPattern; + +import appeng.api.inventories.InternalInventory; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.helpers.patternprovider.PatternContainer; +import appeng.menu.implementations.PatternAccessTermMenu; +import appeng.menu.me.items.PatternEncodingTermMenu; +import net.minecraft.server.level.ServerPlayer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 与反射 / 菜单(PatternAccessTermMenu)相关的工具方法。 + * 包含:获取 PatternAccessTermMenu、通过反射获取 byId/container、列出 provider id、供应器显示名等。 + */ +public final class PatternTerminalUtil { + private PatternTerminalUtil() {} + + /** + * 检查当前菜单是否为ExtendedAE的扩展样板管理终端 + * + * @param player 玩家 + * @return 是否为ExtendedAE扩展终端 + */ + public static boolean isExtendedAETerminal(ServerPlayer player) { + if (player == null || player.containerMenu == null) { + return false; + } + + String containerClassName = player.containerMenu.getClass().getName(); + return containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal"); + } + + + /** + * 通过服务器ID获取PatternContainer + * 兼容ExtendedAE的ContainerExPatternTerminal和原版PatternAccessTermMenu + * + * @param menu 样板访问终端菜单 + * @param providerId 供应器服务器ID + * @return PatternContainer实例,如果不存在则返回null + */ + public static PatternContainer getPatternContainerById(PatternAccessTermMenu menu, long providerId) { + try { + // 通过反射访问byId字段(ExtendedAE继承了这个字段) + Field byIdField = findByIdField(menu.getClass()); + if (byIdField == null) { + System.err.println("ExtendedAE Plus: 无法找到byId字段"); + return null; + } + + byIdField.setAccessible(true); + + @SuppressWarnings("unchecked") + Map byId = (Map) byIdField.get(menu); + + Object containerTracker = byId.get(providerId); + if (containerTracker == null) { + return null; + } + + // 从ContainerTracker中获取PatternContainer + Field containerField = findContainerField(containerTracker.getClass()); + if (containerField == null) { + System.err.println("ExtendedAE Plus: 无法找到container字段"); + return null; + } + + containerField.setAccessible(true); + return (PatternContainer) containerField.get(containerTracker); + + } catch (Exception e) { + System.err.println("ExtendedAE Plus: 无法获取PatternContainer,错误: " + e.getMessage()); + return null; + } + } + + + /** + * 在类层次结构中查找byId字段 + */ + public static Field findByIdField(Class clazz) { + Class currentClass = clazz; + while (currentClass != null) { + try { + return currentClass.getDeclaredField("byId"); + } catch (NoSuchFieldException e) { + currentClass = currentClass.getSuperclass(); + } + } + return null; + } + + /** + * 在类层次结构中查找container字段 + */ + private static Field findContainerField(Class clazz) { + Class currentClass = clazz; + while (currentClass != null) { + try { + return currentClass.getDeclaredField("container"); + } catch (NoSuchFieldException e) { + currentClass = currentClass.getSuperclass(); + } + } + return null; + } + + /** + * 获取玩家当前的样板访问终端菜单(支持ExtendedAE和原版AE2) + * + * @param player 玩家 + * @return PatternAccessTermMenu实例,如果玩家没有打开则返回null + */ + public static PatternAccessTermMenu getPatternAccessMenu(ServerPlayer player) { + if (player == null || player.containerMenu == null) { + return null; + } + // 优先检查ExtendedAE的扩展样板管理终端(使用类名检查避免直接导入) + String containerClassName = player.containerMenu.getClass().getName(); + if (containerClassName.equals("com.glodblock.github.extendedae.container.ContainerExPatternTerminal")) { + // ExtendedAE的容器继承自PatternAccessTermMenu,可以安全转换 + return (PatternAccessTermMenu) player.containerMenu; + } + // 兼容原版AE2的样板访问终端 + if (player.containerMenu instanceof PatternAccessTermMenu) { + return (PatternAccessTermMenu) player.containerMenu; + } + return null; + } + + /** + * 列出当前菜单中所有供应器的服务器ID(原样返回 byId 的 key 集合)。 + */ + public static java.util.List getAllProviderIds(PatternAccessTermMenu menu) { + java.util.List result = new java.util.ArrayList<>(); + if (menu == null) return result; + try { + Field byIdField = PatternTerminalUtil.findByIdField(menu.getClass()); + if (byIdField == null) return result; + byIdField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.Map byId = (java.util.Map) byIdField.get(menu); + if (byId != null) { + result.addAll(byId.keySet()); + } + } catch (Throwable ignored) { + } + return result; + } + + /** + * 基于编码终端菜单的 AE Grid 遍历,列出“可在终端中可见且有空位”的供应器容器。 + * 返回顺序稳定:按 grid 的 machineClasses 顺序,再按 activeMachines 迭代顺序。 + */ + public static List listAvailableProvidersFromGrid(PatternEncodingTermMenu menu) { + List list = new ArrayList<>(); + if (menu == null) return list; + try { + IGridNode node = menu.getNetworkNode(); + if (node == null) return list; + IGrid grid = node.getGrid(); + if (grid == null) return list; + for (var machineClass : grid.getMachineClasses()) { + if (PatternContainer.class.isAssignableFrom(machineClass)) { + @SuppressWarnings("unchecked") + Class containerClass = (Class) machineClass; + for (var container : grid.getActiveMachines(containerClass)) { + if (container == null || !container.isVisibleInTerminal()) continue; + InternalInventory inv = container.getTerminalPatternInventory(); + 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 (hasEmpty) list.add(container); + } + } + } + } catch (Throwable ignored) { + } + return list; + } + +} diff --git a/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java b/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java new file mode 100644 index 0000000..6fcd766 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/uploadPattern/ProviderUploadUtil.java @@ -0,0 +1,238 @@ +package com.extendedae_plus.util.uploadPattern; + +import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.inventories.InternalInventory; +import appeng.helpers.patternprovider.PatternContainer; +import appeng.menu.implementations.PatternAccessTermMenu; +import appeng.menu.me.items.PatternEncodingTermMenu; +import appeng.util.inv.FilteredInternalInventory; +import appeng.util.inv.filter.IAEItemFilter; +import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor; +import com.extendedae_plus.util.PatternProviderDataUtil; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +import java.util.List; + +/** + * 与样板供应器(provider)上传相关的工具类: + * - uploadPatternToProvider (从玩家背包上传) + * - uploadFromEncodingMenuToProvider (从编码终端上传至指定 providerId) + * - uploadFromEncodingMenuToProviderByIndex (按网格顺序 index 上传) + * + * 其中使用 PatternTerminalUtil 提供的反射/容器访问工具。 + */ +public final class ProviderUploadUtil { + private ProviderUploadUtil() {} + + /** + * 发送消息给玩家 + * + * @param player 玩家 + * @param message 消息内容 + */ + private static void sendMessage(ServerPlayer player, String message) { + // 静默:不再向玩家左下角发送任何提示信息 + // 如需恢复,取消下面注释即可: + // if (player != null) { + // player.sendSystemMessage(Component.literal(message)); + // } + // 如果玩家为null,静默忽略(用于测试环境) + } + + /** + * 将玩家背包中的样板上传到指定的样板供应器 + * 兼容ExtendedAE和原版AE2 + * + * @param player 玩家 + * @param playerSlotIndex 玩家背包槽位索引 + * @param providerId 目标样板供应器的服务器ID + * @return 是否上传成功 + */ + public static boolean uploadPatternToProvider(ServerPlayer player, int playerSlotIndex, long providerId) { + // 1. 验证玩家是否打开了样板访问终端 + PatternAccessTermMenu menu = PatternTerminalUtil.getPatternAccessMenu(player); + if (menu == null) { + sendMessage(player, "ExtendedAE Plus: 请先打开样板访问终端或扩展样板管理终端"); + return false; + } + + // 2. 获取玩家背包中的物品 + ItemStack playerItem = player.getInventory().getItem(playerSlotIndex); + if (playerItem.isEmpty()) { + sendMessage(player, "ExtendedAE Plus: 背包槽位为空"); + return false; + } + + // 3. 验证是否是编码样板 + if (!PatternDetailsHelper.isEncodedPattern(playerItem)) { + sendMessage(player, "ExtendedAE Plus: 该物品不是有效的编码样板"); + return false; + } + + // 4. 获取目标样板供应器 + PatternContainer patternContainer = PatternTerminalUtil.getPatternContainerById(menu, providerId); + if (patternContainer == null) { + sendMessage(player, "ExtendedAE Plus: 找不到指定的样板供应器 (ID: " + providerId + ")"); + return false; + } + + // 5. 获取样板供应器的库存 + InternalInventory patternInventory = patternContainer.getTerminalPatternInventory(); + if (patternInventory == null) { + sendMessage(player, "ExtendedAE Plus: 无法访问样板供应器的库存"); + return false; + } + + // 6. 使用AE2的标准样板过滤器进行插入 + var patternFilter = new ExtendedAEPatternFilter(); + var filteredInventory = new FilteredInternalInventory(patternInventory, patternFilter); + + // 7. 尝试插入样板 + ItemStack itemToInsert = playerItem.copy(); + ItemStack remaining = filteredInventory.addItems(itemToInsert); + + if (remaining.getCount() < itemToInsert.getCount()) { + // 插入成功(部分或全部) + int insertedCount = itemToInsert.getCount() - remaining.getCount(); + playerItem.shrink(insertedCount); + + if (playerItem.isEmpty()) { + player.getInventory().setItem(playerSlotIndex, ItemStack.EMPTY); + } + + String terminalType = PatternTerminalUtil.isExtendedAETerminal(player) ? "扩展样板管理终端" : "样板访问终端"; + sendMessage(player, "ExtendedAE Plus: 通过" + terminalType + "成功上传 " + insertedCount + " 个样板"); + return true; + } else { + sendMessage(player, "ExtendedAE Plus: 上传失败 - 样板供应器已满或样板无效"); + return false; + } + } + + /** + * 将图样编码终端的“已编码图样”上传到指定的样板供应器(通过 providerId 定位)。 + */ + public static boolean uploadFromEncodingMenuToProvider(ServerPlayer player, PatternEncodingTermMenu menu, long providerId) { + if (player == null || menu == null) { + return false; + } + var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) + .eap$getEncodedPatternSlot(); + ItemStack stack = encodedSlot.getItem(); + if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { + return false; + } + + PatternAccessTermMenu accessMenu = PatternTerminalUtil.getPatternAccessMenu(player); + if (accessMenu == null) { + return false; + } + // 先确定目标容器名称,用于同名回退 + String targetName = PatternProviderDataUtil.getProviderDisplayName(providerId, accessMenu); + // 构建尝试顺序:先指定ID,其次同名的其他ID + java.util.List tryIds = new java.util.ArrayList<>(); + tryIds.add(providerId); + try { + java.util.List all = PatternTerminalUtil.getAllProviderIds(accessMenu); + for (Long id : all) { + if (id == null || id == providerId) continue; + String name = PatternProviderDataUtil.getProviderDisplayName(id, accessMenu); + if (name != null && name.equals(targetName)) { + tryIds.add(id); + } + } + } catch (Throwable ignored) {} + + // 按顺序逐个尝试插入 + for (Long id : tryIds) { + PatternContainer c = PatternTerminalUtil.getPatternContainerById(accessMenu, id); + if (c == null || !c.isVisibleInTerminal()) continue; + InternalInventory inv = c.getTerminalPatternInventory(); + if (inv == null || inv.size() <= 0) continue; + + var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()); + ItemStack toInsert = stack.copy(); + ItemStack remain = filtered.addItems(toInsert); + if (remain.getCount() < toInsert.getCount()) { + int inserted = toInsert.getCount() - remain.getCount(); + stack.shrink(inserted); + if (stack.isEmpty()) { + encodedSlot.set(ItemStack.EMPTY); + } else { + encodedSlot.set(stack); + } + return true; + } + } + return false; + } + + /** + * 基于“索引”的定向上传:使用 listAvailableProvidersFromGrid(menu) 的顺序, + * 将编码槽样板插入到第 index 个供应器。 + */ + public static boolean uploadFromEncodingMenuToProviderByIndex(ServerPlayer player, PatternEncodingTermMenu menu, int index) { + if (player == null || menu == null || index < 0) return false; + List list = PatternTerminalUtil.listAvailableProvidersFromGrid(menu); + if (index >= list.size()) return false; + var container = list.get(index); + if (container == null) return false; + + var encodedSlot = ((PatternEncodingTermMenuAccessor) (Object) menu) + .eap$getEncodedPatternSlot(); + ItemStack stack = encodedSlot.getItem(); + if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { + return false; + } + + // 以名称为键,同名供应器依次尝试:先 index 指定的,再同名的其他 + String targetName = PatternProviderDataUtil.getProviderDisplayName(container); + java.util.List tryList = new java.util.ArrayList<>(); + tryList.add(container); + try { + for (PatternContainer c : list) { + if (c == null || c == container) continue; + String name = PatternProviderDataUtil.getProviderDisplayName(c); + if (name != null && name.equals(targetName)) { + tryList.add(c); + } + } + } catch (Throwable ignored) {} + + for (PatternContainer c : tryList) { + InternalInventory inv = c.getTerminalPatternInventory(); + if (inv == null || inv.size() <= 0) continue; + var filtered = new FilteredInternalInventory(inv, new ExtendedAEPatternFilter()); + ItemStack toInsert = stack.copy(); + ItemStack remain = filtered.addItems(toInsert); + if (remain.getCount() < toInsert.getCount()) { + int inserted = toInsert.getCount() - remain.getCount(); + stack.shrink(inserted); + if (stack.isEmpty()) { + encodedSlot.set(ItemStack.EMPTY); + } else { + encodedSlot.set(stack); + } + return true; + } + } + return false; + } + + /** + * ExtendedAE兼容的样板过滤器 + * 使用AE2的PatternDetailsHelper进行样板验证 + */ + private static class ExtendedAEPatternFilter implements IAEItemFilter { + @Override + public boolean allowExtract(InternalInventory inv, int slot, int amount) { + return true; + } + + @Override + public boolean allowInsert(InternalInventory inv, int slot, ItemStack stack) { + return !stack.isEmpty() && PatternDetailsHelper.isEncodedPattern(stack); + } + } +}