diff --git a/src/main/java/com/extendedae_plus/mixin/ContainerPatternEncodingTermMenuMixin.java b/src/main/java/com/extendedae_plus/mixin/ContainerPatternEncodingTermMenuMixin.java new file mode 100644 index 0000000..5913315 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ContainerPatternEncodingTermMenuMixin.java @@ -0,0 +1,72 @@ +package com.extendedae_plus.mixin; + +import appeng.menu.me.items.PatternEncodingTermMenu; +import com.extendedae_plus.util.ExtendedAEPatternUploadUtil; +import com.glodblock.github.glodium.network.packet.sync.IActionHolder; +import com.glodblock.github.glodium.network.packet.sync.Paras; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; +import java.util.function.Consumer; + +/** + * 给 AE2 的 PatternEncodingTermMenu 增加一个通用动作持有者,实现接收 EPP 的 CGenericPacket 动作。 + * 注册动作 "upload_to_matrix":仅上传“合成图样”到 ExtendedAE 装配矩阵。 + */ +@Mixin(PatternEncodingTermMenu.class) +public abstract class ContainerPatternEncodingTermMenuMixin implements IActionHolder { + + @Unique + private final Map> actions = createHolder(); + + @Unique + private Player epp$player; + + // AE2 终端主构造:PatternEncodingTermMenu(int id, Inventory ip, IPatternTerminalMenuHost host) + @Inject(method = "(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;)V", at = @At("TAIL"), remap = false) + private void epp$ctorA(int id, net.minecraft.world.entity.player.Inventory ip, appeng.helpers.IPatternTerminalMenuHost host, CallbackInfo ci) { + this.epp$player = ip.player; + // 注册动作:无参,由服务端直接读 encoded 槽位。 + System.out.println("[EAE+][Server] Register action 'upload_to_matrix' for PatternEncodingTermMenu ctorA"); + this.actions.put("upload_to_matrix", p -> { + try { + var sp = (ServerPlayer) this.epp$player; + System.out.println("[EAE+][Server] Handle action 'upload_to_matrix' from " + sp.getGameProfile().getName()); + ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, (PatternEncodingTermMenu) (Object) this); + } catch (Throwable t) { + System.out.println("[EAE+][Server] Exception in 'upload_to_matrix': " + t); + t.printStackTrace(); + } + }); + } + + // AE2 另一个构造:PatternEncodingTermMenu(MenuType, int, Inventory, IPatternTerminalMenuHost, boolean) + @Inject(method = "(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;Z)V", at = @At("TAIL"), remap = false) + private void epp$ctorB(net.minecraft.world.inventory.MenuType menuType, int id, net.minecraft.world.entity.player.Inventory ip, appeng.helpers.IPatternTerminalMenuHost host, boolean bindInventory, CallbackInfo ci) { + this.epp$player = ip.player; + System.out.println("[EAE+][Server] Register action 'upload_to_matrix' for PatternEncodingTermMenu ctorB"); + this.actions.put("upload_to_matrix", p -> { + try { + var sp = (ServerPlayer) this.epp$player; + System.out.println("[EAE+][Server] Handle action 'upload_to_matrix' from " + sp.getGameProfile().getName()); + ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToMatrix(sp, (PatternEncodingTermMenu) (Object) this); + } catch (Throwable t) { + System.out.println("[EAE+][Server] Exception in 'upload_to_matrix': " + t); + t.printStackTrace(); + } + }); + } + + @NotNull + @Override + public Map> getActionMap() { + return this.actions; + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ContainerUWirelessExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/ContainerUWirelessExPatternTerminalMixin.java index e5f2196..bcc51c7 100644 --- a/src/main/java/com/extendedae_plus/mixin/ContainerUWirelessExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ContainerUWirelessExPatternTerminalMixin.java @@ -33,7 +33,7 @@ public abstract class ContainerUWirelessExPatternTerminalMixin implements IActio private Player epp$player; // 明确目标构造签名:(int, Inventory, HostUWirelessExPAT) - @Inject(method = "(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/xmod/wt/HostUWirelessExPAT;)V", at = @At("TAIL")) + @Inject(method = "(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/xmod/wt/HostUWirelessExPAT;)V", at = @At("TAIL"), remap = false) private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, HostUWirelessExPAT host, CallbackInfo ci) { this.epp$player = playerInventory.player; // 注册上传动作:参数顺序必须与客户端 CGenericPacket 保持一致 diff --git a/src/main/java/com/extendedae_plus/mixin/GuiPatternEncodingTermMixin.java b/src/main/java/com/extendedae_plus/mixin/GuiPatternEncodingTermMixin.java new file mode 100644 index 0000000..76e5114 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/GuiPatternEncodingTermMixin.java @@ -0,0 +1,70 @@ +package com.extendedae_plus.mixin; + +import com.extendedae_plus.mixin.accessor.AEBaseScreenAccessor; +import appeng.client.gui.me.items.PatternEncodingTermScreen; +import appeng.client.gui.widgets.IconButton; +import appeng.client.gui.Icon; +import appeng.client.gui.style.ScreenStyle; +import appeng.menu.me.items.PatternEncodingTermMenu; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.glodblock.github.extendedae.network.EPPNetworkHandler; +import com.glodblock.github.glodium.network.packet.CGenericPacket; + +/** + * 在 AE2 的 PatternEncodingTermScreen 界面中,给编码按钮旁添加一个“上传到矩阵”按钮。 + * 客户端点击后,使用 ExtendedAE 的网络通道发送通用数据包到服务端,由容器 Mixin 处理。 + */ +@Mixin(PatternEncodingTermScreen.class) +public abstract class GuiPatternEncodingTermMixin { + + @Unique + private IconButton epp$uploadButton; + + // 通过 Accessor 调用 AEBaseScreen#addToLeftToolbar + + // 在构造函数尾部创建按钮实例(仅创建,不添加) + @Inject(method = "", at = @At("TAIL"), remap = false) + private void epp$onInit(C menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 选择一个合适的图标占位(AE2 暂无显式“上传”图标,这里先用 PATTERN_ACCESS_SHOW) + this.epp$uploadButton = new IconButton(b -> { + // 直接发送无参动作,服务端容器会根据当前 encoded 槽位处理 + System.out.println("[EAE+][Client] Click upload button -> send CGenericPacket('upload_to_matrix')"); + try { + var mc = net.minecraft.client.Minecraft.getInstance(); + var cm = mc.player != null ? mc.player.containerMenu : null; + System.out.println("[EAE+][Client] Current container: " + (cm == null ? "null" : cm.getClass().getName())); + if (cm instanceof com.glodblock.github.glodium.network.packet.sync.IActionHolder ah) { + var keys = ah.getActionMap().keySet(); + System.out.println("[EAE+][Client] IActionHolder detected. actions=" + keys); + } else { + System.out.println("[EAE+][Client] Current container is NOT IActionHolder"); + } + } catch (Throwable t) { + System.out.println("[EAE+][Client] Inspect container failed: " + t); + } + EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("upload_to_matrix")); + }) { + @Override + protected Icon getIcon() { + return Icon.PATTERN_ACCESS_SHOW; + } + }; + this.epp$uploadButton.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.upload_to_matrix"))); + System.out.println("[EAE+][Client] Created upload button in PatternEncodingTermScreen ctor tail"); + // 直接在构造尾部加入左侧工具栏,避免目标类未覆写 init 导致的注入不到 + ((AEBaseScreenAccessor) (Object) this).epp$addToLeftToolbar(this.epp$uploadButton); + System.out.println("[EAE+][Client] Added upload button to left toolbar in ctor tail"); + } + +} diff --git a/src/main/java/com/extendedae_plus/mixin/accessor/AEBaseScreenAccessor.java b/src/main/java/com/extendedae_plus/mixin/accessor/AEBaseScreenAccessor.java new file mode 100644 index 0000000..0bf26f1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/accessor/AEBaseScreenAccessor.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.mixin.accessor; + +import net.minecraft.client.gui.components.Button; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; +import appeng.client.gui.AEBaseScreen; + +@Mixin(AEBaseScreen.class) +public interface AEBaseScreenAccessor { + @Invoker(value = "addToLeftToolbar", remap = false) + B epp$addToLeftToolbar(B button); +} diff --git a/src/main/java/com/extendedae_plus/mixin/accessor/PatternEncodingTermMenuAccessor.java b/src/main/java/com/extendedae_plus/mixin/accessor/PatternEncodingTermMenuAccessor.java new file mode 100644 index 0000000..23af9b5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/accessor/PatternEncodingTermMenuAccessor.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.mixin.accessor; + +import appeng.menu.me.items.PatternEncodingTermMenu; +import appeng.menu.slot.RestrictedInputSlot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PatternEncodingTermMenu.class) +public interface PatternEncodingTermMenuAccessor { + @Accessor("encodedPatternSlot") + RestrictedInputSlot epp$getEncodedPatternSlot(); +} diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 81aa599..e8d4931 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -2,6 +2,8 @@ package com.extendedae_plus.util; import appeng.api.inventories.InternalInventory; import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; import appeng.helpers.patternprovider.PatternContainer; import appeng.menu.implementations.PatternAccessTermMenu; import appeng.util.inv.FilteredInternalInventory; @@ -12,6 +14,14 @@ import net.minecraft.network.chat.Component; import java.lang.reflect.Field; import java.util.Map; +import java.util.Set; + +import appeng.menu.me.items.PatternEncodingTermMenu; +import appeng.api.crafting.IPatternDetails; +import appeng.crafting.pattern.AECraftingPattern; +import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.items.IItemHandler; /** * ExtendedAE扩展样板管理终端专用的样板上传工具类 @@ -29,22 +39,180 @@ public class ExtendedAEPatternUploadUtil { 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) { + System.out.println("[EAE+][Server] uploadFromEncodingMenuToMatrix: player or menu is null"); + return false; + } + + // 读取已编码槽位的物品 + var encodedSlot = ((com.extendedae_plus.mixin.accessor.PatternEncodingTermMenuAccessor) (Object) menu) + .epp$getEncodedPatternSlot(); + ItemStack stack = encodedSlot.getItem(); + System.out.println("[EAE+][Server] Encoded slot stack: " + stack + ", count=" + stack.getCount()); + if (stack.isEmpty() || !PatternDetailsHelper.isEncodedPattern(stack)) { + sendMessage(player, "ExtendedAE Plus: 没有可上传的编码样板"); + System.out.println("[EAE+][Server] Fail: stack empty or not encoded pattern"); + return false; + } + + // 仅允许“合成图样” + IPatternDetails details = PatternDetailsHelper.decodePattern(stack, player.level()); + System.out.println("[EAE+][Server] Decoded details: " + (details == null ? "null" : details.getClass().getName())); + if (!(details instanceof AECraftingPattern)) { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_not_crafting"); + System.out.println("[EAE+][Server] Fail: not AECraftingPattern"); + return false; + } + + // 获取 AE 网络 + IGridNode node = menu.getNetworkNode(); + System.out.println("[EAE+][Server] Grid node: " + node); + if (node == null) { + sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中"); + System.out.println("[EAE+][Server] Fail: grid node null"); + return false; + } + IGrid grid = node.getGrid(); + System.out.println("[EAE+][Server] Grid: " + grid); + if (grid == null) { + sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中"); + System.out.println("[EAE+][Server] Fail: grid null"); + return false; + } + + // 查找已成型的装配矩阵(图样模块)的内部库存(优先使用内部库存,其带有正确过滤逻辑) + InternalInventory matrixInv = findFirstMatrixPatternInventory(grid); + System.out.println("[EAE+][Server] Matrix internal inventory: " + matrixInv); + if (matrixInv == null) { + // 回退:尝试 Forge 能力(旧实现) + IItemHandler cap = findFirstMatrixPatternHandler(grid); + System.out.println("[EAE+][Server] Fallback Matrix item handler: " + cap); + if (cap == null) { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_no_matrix"); + System.out.println("[EAE+][Server] Fail: no formed matrix found"); + return false; + } + // 使用能力插入 + ItemStack toInsert = stack.copy(); + System.out.println("[EAE+][Server] Try insert via capability, count=" + toInsert.getCount()); + ItemStack remain = insertIntoAnySlot(cap, toInsert); + System.out.println("[EAE+][Server] Insert remain count=" + remain.getCount()); + 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"); + System.out.println("[EAE+][Server] Success via capability: inserted=" + inserted); + return true; + } else { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full"); + System.out.println("[EAE+][Server] Fail via capability: inventory full or cannot accept pattern"); + return false; + } + } + + // 通过内部库存插入(遵循其过滤规则) + ItemStack toInsert = stack.copy(); + System.out.println("[EAE+][Server] Try insert via internal inventory, count=" + toInsert.getCount()); + ItemStack remain = matrixInv.addItems(toInsert); + System.out.println("[EAE+][Server] Internal inventory remain count=" + remain.getCount()); + 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"); + System.out.println("[EAE+][Server] Success via internal inventory: inserted=" + inserted); + return true; + } else { + sendMessage(player, "extendedae_plus.upload_to_matrix.fail_full"); + System.out.println("[EAE+][Server] Fail via internal inventory: inventory full or cannot accept pattern"); + return false; + } + } + + /** + * 在给定 AE Grid 中查找第一台已成型且在线的装配矩阵“图样模块”,并返回其用于外部插入的内部库存。 + * 优先使用 TileAssemblerMatrixPattern#getExposedInventory(仅允许插入,且已带AE过滤规则)。 + */ + private static InternalInventory findFirstMatrixPatternInventory(IGrid grid) { + try { + var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class); + for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) { + if (tile != null && tile.isFormed() && tile.getMainNode().isActive()) { + var inv = tile.getExposedInventory(); + if (inv != null) { + return inv; + } + } + } + } catch (Throwable t) { + System.out.println("[EAE+][Server] findFirstMatrixPatternInventory exception: " + t); + } + return null; + } + + /** + * 在给定 AE Grid 中查找第一台已成型的装配矩阵,并返回其聚合图样仓的 IItemHandler。 + */ + private static IItemHandler findFirstMatrixPatternHandler(IGrid grid) { + try { + Set matrices = grid.getMachines(TileAssemblerMatrixBase.class); + for (TileAssemblerMatrixBase tile : matrices) { + if (tile != null && tile.isFormed()) { + var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null); + if (capOpt != null) { + var handler = capOpt.orElse(null); + if (handler != null) { + return handler; + } + } + } + } + } catch (Throwable ignored) { + } + return null; + } + + /** + * 尝试将整个物品栈插入到 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; + } + /** * 检查当前菜单是否为ExtendedAE的扩展样板管理终端 * diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 12caea2..62a3673 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -5,5 +5,10 @@ "gui.expatternprovider.clear_selection": "取消选择", "block.extendedae_plus.wireless_transceiver": "无线收发器", "item.extendedae_plus.wireless_transceiver": "无线收发器", - "itemGroup.extendedae_plus.main": "扩展AE Plus" + "itemGroup.extendedae_plus.main": "扩展AE Plus", + "extendedae_plus.upload_to_matrix": "上传到装配矩阵", + "extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵", + "extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略", + "extendedae_plus.upload_to_matrix.fail_no_matrix": "未在当前网络中找到已成型的装配矩阵", + "extendedae_plus.upload_to_matrix.fail_full": "装配矩阵的样板仓已满或无法插入" } \ No newline at end of file diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 5116de5..1073507 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -7,7 +7,9 @@ "GuiExPatternProviderMixin", "SlotGridLayoutMixin", "GuiExPatternTerminalMixin", - "HighlightButtonMixin" + "HighlightButtonMixin", + "GuiPatternEncodingTermMixin", + "accessor.AEBaseScreenAccessor" ], "mixins": [ "ContainerExPatternProviderMixin", @@ -16,8 +18,9 @@ "ContainerUWirelessExPatternTerminalMixin", "TileExPatternProviderMixin", "PartExPatternProviderMixin", - "PatternEncodingTermMenuMixin", - "accessor.MEStorageMenuAccessor" + "ContainerPatternEncodingTermMenuMixin", + "accessor.MEStorageMenuAccessor", + "accessor.PatternEncodingTermMenuAccessor" ], "injectors": { "defaultRequire": 1