package com.extendedae_plus.mixin; import appeng.client.gui.Icon; import appeng.client.gui.implementations.PatternProviderScreen; import appeng.client.gui.style.PaletteColor; import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.widgets.VerticalButtonBar; import appeng.menu.SlotSemantics; import com.extendedae_plus.NewIcon; import com.extendedae_plus.util.PatternProviderUIHelper; import com.glodblock.github.extendedae.client.button.ActionEPPButton; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; import com.glodblock.github.extendedae.container.ContainerExPatternProvider; import com.extendedae_plus.network.UpdatePagePacket; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.Slot; 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.lang.reflect.Field; import java.util.List; @Mixin(GuiExPatternProvider.class) public abstract class GuiExPatternProviderMixin extends PatternProviderScreen { @Unique ScreenStyle screenStyle; @Unique private VerticalButtonBar rightToolbar; @Unique private static final int SLOTS_PER_PAGE = 36; // 每页显示36个槽位 public GuiExPatternProviderMixin(ContainerExPatternProvider menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } @Unique public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { super.render(guiGraphics, mouseX, mouseY, partialTicks); int maxSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size(); // 只有当槽位数超过每页显示数量时才显示翻页信息 if (maxSlots > SLOTS_PER_PAGE) { Font fontRenderer = Minecraft.getInstance().font; // 获取当前页码 int currentPage = getCurrentPage(); int maxPage = getMaxPage(); // 获取ae通用界面样式 int color = screenStyle.getColor(PaletteColor.DEFAULT_TEXT_COLOR).toARGB(); // 调整页码显示位置:在"样板"文字的右边 guiGraphics.drawString(font, Component.literal("第 " + (currentPage + 1) + "/" + maxPage + " 页"), leftPos + 8 + 50, topPos + 30, color, false); } } @Unique public void updateBeforeRender() { super.updateBeforeRender(); try { ContainerExPatternProvider menu1 = this.getMenu(); // 调用showPage方法 java.lang.reflect.Method showPageMethod = menu1.getClass().getMethod("showPage"); showPageMethod.invoke(menu1); // 获取当前页码和最大页码 Field fieldPage = menu1.getClass().getDeclaredField("page"); fieldPage.setAccessible(true); Integer page = (Integer) fieldPage.get(menu1); Field fieldMaxPage = menu1.getClass().getDeclaredField("maxPage"); fieldMaxPage.setAccessible(true); Integer maxPage = (Integer) fieldMaxPage.get(menu1); // 更新按钮可见性 - 始终显示,支持循环翻页 if (nextPage != null && prevPage != null) { this.nextPage.setVisibility(true); this.prevPage.setVisibility(true); } if (x2Button != null) { this.x2Button.setVisibility(true); } if (divideBy2Button != null) { this.divideBy2Button.setVisibility(true); } // 调整槽位位置 this.adjustSlotPositions(page); } catch (Exception e) { // 忽略反射错误 } } @Unique private void adjustSlotPositions(int currentPage) { try { List slots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN); int totalSlots = slots.size(); if (totalSlots <= SLOTS_PER_PAGE) { return; // 不需要翻页 } int slot_id = 0; for (Slot s : slots) { int page_id = slot_id / SLOTS_PER_PAGE; if (page_id == currentPage) { // 当前页的槽位需要调整位置 int slotInPage = slot_id % SLOTS_PER_PAGE; int row = slotInPage / 9; // 0-3 int col = slotInPage % 9; // 0-8 // 计算目标位置(始终在前4行) int x = 8 + col * 18; int y = 42 + row * 18; // 使用反射设置槽位位置,支持混淆环境 Field xField = null; Field yField = null; // 尝试不同的字段名(开发环境和生产环境可能不同) String[] xFieldNames = {"x", "field_75262_c"}; String[] yFieldNames = {"y", "field_75263_d"}; for (String fieldName : xFieldNames) { try { xField = Slot.class.getDeclaredField(fieldName); xField.setAccessible(true); break; } catch (NoSuchFieldException ignored) {} } for (String fieldName : yFieldNames) { try { yField = Slot.class.getDeclaredField(fieldName); yField.setAccessible(true); break; } catch (NoSuchFieldException ignored) {} } if (xField != null && yField != null) { xField.set(s, x); yField.set(s, y); } } ++slot_id; } } catch (Exception e) { // 忽略反射错误 } } @Unique private int getCurrentPage() { try { ContainerExPatternProvider menu1 = this.getMenu(); Field fieldPage = menu1.getClass().getDeclaredField("page"); fieldPage.setAccessible(true); return (Integer) fieldPage.get(menu1); } catch (Exception e) { return 0; } } @Unique private int getMaxPage() { try { ContainerExPatternProvider menu1 = this.getMenu(); Field fieldMaxPage = menu1.getClass().getDeclaredField("maxPage"); fieldMaxPage.setAccessible(true); return (Integer) fieldMaxPage.get(menu1); } catch (Exception e) { return 1; } } public ActionEPPButton nextPage; public ActionEPPButton prevPage; public ActionEPPButton x2Button; public ActionEPPButton divideBy2Button; @Inject(method = "", at = @At("RETURN"), remap = false) private void injectInit(ContainerExPatternProvider menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { this.screenStyle = style; this.rightToolbar = new VerticalButtonBar(); // 只有当槽位数超过每页显示数量时才添加翻页按钮 int maxSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size(); if (maxSlots > SLOTS_PER_PAGE) { // 前进后退按钮 this.prevPage = new ActionEPPButton((b) -> { int currentPage = getCurrentPage(); int maxPage = getMaxPage(); // 循环翻页:第一页向前翻到最后一页 int newPage = (currentPage - 1 + maxPage) % maxPage; try { ContainerExPatternProvider menu1 = this.getMenu(); java.lang.reflect.Method setPageMethod = menu1.getClass().getMethod("setPage", int.class); setPageMethod.invoke(menu1, newPage); } catch (Exception e) { // 忽略反射错误 } }, Icon.ARROW_LEFT); this.nextPage = new ActionEPPButton((b) -> { int currentPage = getCurrentPage(); int maxPage = getMaxPage(); // 循环翻页:最后一页向后翻到第一页 int newPage = (currentPage + 1) % maxPage; try { ContainerExPatternProvider menu1 = this.getMenu(); java.lang.reflect.Method setPageMethod = menu1.getClass().getMethod("setPage", int.class); setPageMethod.invoke(menu1, newPage); } catch (Exception e) { // 忽略反射错误 } }, Icon.ARROW_RIGHT); this.addToLeftToolbar(this.nextPage); this.addToLeftToolbar(this.prevPage); } // x2 按钮 - 单机模式直接调用服务器端逻辑 this.x2Button = new ActionEPPButton((b) -> { try { // 单机模式:直接在逻辑服务器端执行样板倍增 net.minecraft.client.Minecraft minecraft = net.minecraft.client.Minecraft.getInstance(); if (minecraft.level != null && minecraft.player != null) { // 获取逻辑服务器端的玩家实例 net.minecraft.server.level.ServerPlayer serverPlayer = minecraft.getSingleplayerServer() .getPlayerList().getPlayer(minecraft.player.getUUID()); if (serverPlayer != null) { // 在服务器端执行样板倍增逻辑 executePatternScalingOnServer(serverPlayer, "MULTIPLY", 2.0); } else { System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例"); } } else { System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null"); } } catch (Exception e) { System.out.println("ExtendedAE Plus: 执行样板倍增时发生错误:" + e.getMessage()); e.printStackTrace(); } }, NewIcon.MULTIPLY2); // /2 按钮 - 单机模式直接调用服务器端逻辑 this.divideBy2Button = new ActionEPPButton((b) -> { try { // 单机模式:直接在逻辑服务器端执行样板除法 net.minecraft.client.Minecraft minecraft = net.minecraft.client.Minecraft.getInstance(); if (minecraft.level != null && minecraft.player != null) { // 获取逻辑服务器端的玩家实例 net.minecraft.server.level.ServerPlayer serverPlayer = minecraft.getSingleplayerServer() .getPlayerList().getPlayer(minecraft.player.getUUID()); if (serverPlayer != null) { // 在服务器端执行样板除法逻辑 executePatternScalingOnServer(serverPlayer, "DIVIDE", 2.0); } else { System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例"); } } else { System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null"); } } catch (Exception e) { System.out.println("ExtendedAE Plus: 执行样板除法时发生错误:" + e.getMessage()); e.printStackTrace(); } }, NewIcon.DIVIDE2); this.addToLeftToolbar(this.x2Button); this.addToLeftToolbar(this.divideBy2Button); } /** * 在服务器端执行样板缩放操作(单机模式) */ @Unique private void executePatternScalingOnServer(net.minecraft.server.level.ServerPlayer serverPlayer, String scalingType, double scaleFactor) { try { // 将实际逻辑切换到服务端主线程执行,避免跨线程访问导致读取到空库存 serverPlayer.getServer().execute(() -> { try { // 直接基于容器槽位操作,完全绕开 PatternProviderLogic 及其内部字段 if (!(serverPlayer.containerMenu instanceof com.glodblock.github.extendedae.container.ContainerExPatternProvider exMenu)) { System.out.println("ExtendedAE Plus: 当前容器不是 ExPatternProvider,无法执行样板缩放"); return; } int scaled = 0; int failed = 0; int total = 0; final int scale = (int) Math.round(scaleFactor); final boolean div = !"MULTIPLY".equals(scalingType); java.util.List slots = exMenu.getSlots(appeng.menu.SlotSemantics.ENCODED_PATTERN); for (var slot : slots) { var stack = slot.getItem(); if (stack.getItem() instanceof appeng.crafting.pattern.EncodedPatternItem patternItem) { total++; var detail = patternItem.decode(stack, serverPlayer.level(), false); if (detail instanceof appeng.crafting.pattern.AEProcessingPattern process) { var input = process.getSparseInputs(); var output = process.getOutputs(); // 检查是否可修改(来源:ExtendedAE ContainerPatternModifier.checkModify) if (checkModifyLikeExtendedAE(input, scale, div) && checkModifyLikeExtendedAE(output, scale, div)) { var mulInput = new appeng.api.stacks.GenericStack[input.length]; var mulOutput = new appeng.api.stacks.GenericStack[output.length]; modifyStacksLikeExtendedAE(input, mulInput, scale, div); modifyStacksLikeExtendedAE(output, mulOutput, scale, div); var newPattern = appeng.api.crafting.PatternDetailsHelper.encodeProcessingPattern(mulInput, mulOutput); if (slot instanceof appeng.menu.slot.AppEngSlot as) { as.set(newPattern); } else { slot.set(newPattern); } scaled++; } else { failed++; } } else { // 非处理样板:跳过 failed++; } } } // 构造结果并回显 String message; if (scaled == 0) { message = String.format( "ℹ️ ExtendedAE Plus: 样板%s完成,但未处理任何样板。共发现 %d 个样板,失败 %d 个(可能全为合成样板或数量不满足条件)", div ? "除法" : "倍增", total, failed); } else if (failed > 0) { message = String.format("✅ ExtendedAE Plus: 样板%s完成!处理了 %d 个,跳过 %d 个", div ? "除法" : "倍增", scaled, failed); } else { message = String.format("✅ ExtendedAE Plus: 样板%s成功!处理了 %d 个", div ? "除法" : "倍增", scaled); } var minecraft = net.minecraft.client.Minecraft.getInstance(); if (minecraft.player != null) { minecraft.player.displayClientMessage(net.minecraft.network.chat.Component.literal(message), true); } System.out.println("ExtendedAE Plus: " + message); } catch (Exception e) { System.out.println("ExtendedAE Plus: 服务器端执行样板缩放时发生错误: " + e.getMessage()); e.printStackTrace(); } }); } catch (Exception e) { System.out.println("ExtendedAE Plus: 调度到服务器主线程时发生错误: " + e.getMessage()); e.printStackTrace(); } } @Unique private boolean checkModifyLikeExtendedAE(appeng.api.stacks.GenericStack[] stacks, int scale, boolean div) { if (div) { for (var stack : stacks) { if (stack != null) { if (stack.amount() % scale != 0) { return false; } } } } else { for (var stack : stacks) { if (stack != null) { long upper = 999999L * stack.what().getAmountPerUnit(); if (stack.amount() * scale > upper) { return false; } } } } return true; } @Unique private void modifyStacksLikeExtendedAE(appeng.api.stacks.GenericStack[] stacks, appeng.api.stacks.GenericStack[] des, int scale, boolean div) { for (int i = 0; i < stacks.length; i++) { if (stacks[i] != null) { long amt = div ? stacks[i].amount() / scale : stacks[i].amount() * scale; des[i] = new appeng.api.stacks.GenericStack(stacks[i].what(), amt); } } } }