From b6b9c14446d4567ce8486e9d5b83c3f0e88588bc Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:31:01 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E4=B8=BA=E7=AE=A1=E7=90=86=E7=BB=88?= =?UTF-8?q?=E7=AB=AF=E5=A2=9E=E5=8A=A0=E6=89=93=E5=BC=80=E6=9C=BA=E5=99=A8?= =?UTF-8?q?ui=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 - gradle.properties | 2 +- .../ContainerExPatternTerminalMixin.java | 131 ++++++++++++- .../extendedae/GuiExPatternTerminalMixin.java | 181 +++++++++++++++++- 4 files changed, 310 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index d31f0f1..64544ed 100644 --- a/build.gradle +++ b/build.gradle @@ -102,9 +102,6 @@ dependencies { //jec modCompileOnly "curse.maven:just-enough-characters-250702:6680042" - //mae2 - modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6342203" - modCompileOnly "curse.maven:modern-ae2-additions-1028068:6342203" } allprojects { diff --git a/gradle.properties b/gradle.properties index d28f3d4..b3b0a9f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.3.3 +mod_version = 1.3.4-beta maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/ContainerExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/ContainerExPatternTerminalMixin.java index b408a42..fde3756 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/ContainerExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/ContainerExPatternTerminalMixin.java @@ -8,6 +8,21 @@ 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 net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.core.registries.Registries; +import net.minecraftforge.network.NetworkHooks; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -17,11 +32,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Map; import java.util.function.Consumer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; @Mixin(ContainerExPatternTerminal.class) public abstract class ContainerExPatternTerminalMixin implements IActionHolder { - @GuiSync(11452) + @GuiSync(25564) @Unique public boolean eap$hidePatternSlots = false; @@ -41,13 +58,19 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder { } @Unique - private final Map> eap$actions = createHolder(); + private Map> eap$actions; @Unique private Player epp$player; + @Unique + private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus"); + @Inject(method = "*", at = @At("TAIL")) private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, IConfigurableObject host, CallbackInfo ci) { + if (this.eap$actions == null) { + this.eap$actions = createHolder(); + } this.epp$player = playerInventory.player; // 注册上传动作:参数顺序必须与客户端 CGenericPacket 保持一致 this.eap$actions.put("upload", p -> { @@ -61,6 +84,110 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder { } catch (Throwable ignored) { } }); + + // 注册打开UI动作:open_ui(posLong, dimensionId, faceOrdinal?) + this.eap$actions.put("open_ui", p -> { + try { + // 参数解析 + Object po = p.get(0); // BlockPos as long (BlockPos#asLong) + Object do0 = p.get(1); // Dimension id string (e.g., minecraft:overworld) + Object fo; + try { + fo = p.get(2); // Optional face ordinal + } catch (Throwable __ignored) { + fo = null; + } + + long posLong = (po instanceof Number) ? ((Number) po).longValue() : Long.parseLong(String.valueOf(po)); + String dimStr = String.valueOf(do0); + int faceOrd = -1; + if (fo != null) { + faceOrd = (fo instanceof Number) ? ((Number) fo).intValue() : Integer.parseInt(String.valueOf(fo)); + } + + BlockPos pos = BlockPos.of(posLong); + ResourceLocation dimId = ResourceLocation.tryParse(dimStr); + if (dimId == null) { + EAP_LOGGER.warn("[EPlus] open_ui: invalid dim '{}'", dimStr); + return; + } + ResourceKey dimKey = ResourceKey.create(Registries.DIMENSION, dimId); + + if (!(this.epp$player instanceof ServerPlayer sp)) { + EAP_LOGGER.warn("[EPlus] open_ui: not a ServerPlayer"); + return; + } + + ServerLevel level = sp.server.getLevel(dimKey); + if (level == null) { + EAP_LOGGER.warn("[EPlus] open_ui: level null for key {}", dimKey); + return; + } + + EAP_LOGGER.debug("[EPlus] open_ui: pos={}, dim={}, faceOrd={}", pos, dimKey.location(), faceOrd); + + // 目标应为供应器所面向/连接的相邻方块,而非供应器自身 + Direction[] tries = (faceOrd >= 0 && faceOrd < Direction.values().length) + ? new Direction[]{Direction.values()[faceOrd]} + : Direction.values(); + + // 1) 先尝试在相邻方块直接打开 MenuProvider + for (Direction dir : tries) { + BlockPos targetPos = pos.relative(dir); + BlockEntity be = level.getBlockEntity(targetPos); + if (be instanceof MenuProvider provider) { + NetworkHooks.openScreen(sp, provider, targetPos); + EAP_LOGGER.debug("[EPlus] open_ui: opened BE MenuProvider at {} (neighbor via {})", targetPos, dir); + return; + } + var state = level.getBlockState(targetPos); + MenuProvider provider = state.getMenuProvider(level, targetPos); + if (provider != null) { + NetworkHooks.openScreen(sp, provider, targetPos); + EAP_LOGGER.debug("[EPlus] open_ui: opened State MenuProvider at {} (neighbor via {})", targetPos, dir); + return; + } + } + + // 2) 兜底:为避免误触发放置/覆盖,仅在手上至少有一只手为空时,使用 BlockState.use 进行一次“徒手交互” + boolean hasFace = (faceOrd >= 0 && faceOrd < Direction.values().length); + boolean anyHandEmpty = sp.getMainHandItem().isEmpty() || sp.getOffhandItem().isEmpty(); + if (anyHandEmpty) { + InteractionHand hand = sp.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; + if (hasFace) { + Direction dir = Direction.values()[faceOrd]; + BlockPos targetPos = pos.relative(dir); + var state2 = level.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false); + InteractionResult r = state2.use(level, sp, hand, hit); + EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (via {}), result={}", targetPos, dir.getOpposite(), dir, r); + } else { + // 无朝向:优先尝试有方块实体的邻居,否则尝试实心方块邻居,各只尝试一次 + Direction chosen = null; + for (Direction d : Direction.values()) { + if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; } + } + if (chosen == null) { + for (Direction d : Direction.values()) { + if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; } + } + } + if (chosen != null) { + BlockPos targetPos = pos.relative(chosen); + var state2 = level.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); + InteractionResult r = state2.use(level, sp, hand, hit); + EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (auto via {}), result={}", targetPos, chosen.getOpposite(), chosen, r); + } else { + EAP_LOGGER.debug("[EPlus] open_ui: no neighbor candidate for fallback (faceOrd<0)"); + } + } + } else { + EAP_LOGGER.debug("[EPlus] open_ui: skip fallback (hands occupied)"); + } + } catch (Throwable ignored) { + } + }); } @NotNull diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index e529c5c..0a1cf72 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -11,6 +11,7 @@ import appeng.client.gui.widgets.IconButton; import appeng.menu.AEBaseMenu; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.renderer.Rect2i; import net.minecraft.network.chat.Component; @@ -29,9 +30,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; @Pseudo -@Mixin(GuiExPatternTerminal.class) +@Mixin(value = GuiExPatternTerminal.class, remap = false) public abstract class GuiExPatternTerminalMixin extends AEBaseScreen { @Unique @@ -46,6 +51,14 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen private boolean eap$showSlots = false; // 默认显示槽位 @Unique private long eap$currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID + @Unique + private final Map eap$openUIButtons = new HashMap<>(); + + @Unique + private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus"); + + @Unique + private boolean eap$debugLoggedOnce = false; @Shadow(remap = false) private AETextField searchOutField; @Shadow(remap = false) private AETextField searchInField; @Shadow(remap = false) private Set matchedStack; @@ -181,6 +194,97 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen } } + @Unique + private int getIntConst(Class cls, String name, int defVal) { + try { + var f = cls.getDeclaredField(name); + f.setAccessible(true); + return (int) f.get(null); + } catch (Throwable t) { + return defVal; + } + } + + @Unique + private void eap$tryOpenProviderUI(int rowIndex) { + try { + // 使用 Accessor 获取 rows,避免取到父类导致失败 + com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc = + (com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this; + java.util.ArrayList rows = acc.getRows(); + + // 找到该分组对应的第一个 PatternContainerRecord + Class cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class; + var byGroupField = cls.getDeclaredField("byGroup"); + byGroupField.setAccessible(true); + Object byGroup = byGroupField.get(this); // HashMultimap + + Object headerRow = rows.get(rowIndex); + var groupField = headerRow.getClass().getDeclaredField("group"); + groupField.setAccessible(true); + Object group = groupField.get(headerRow); + + // 调用 byGroup.get(group),再取第一个元素 + java.util.Collection containers = (java.util.Collection) byGroup.getClass().getMethod("get", Object.class).invoke(byGroup, group); + if (containers == null || containers.isEmpty()) { + return; + } + Object firstRecord = containers.iterator().next(); // PatternContainerRecord + long serverId = (long) firstRecord.getClass().getMethod("getServerId").invoke(firstRecord); + + // 通过 infoMap 获取位置信息 + var infoMapField = cls.getDeclaredField("infoMap"); + infoMapField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.HashMap infoMap = (java.util.HashMap) infoMapField.get(this); + Object info = infoMap.get(serverId); + if (info == null) { + // 无位置信息,提示 + if (this.minecraft != null && this.minecraft.player != null) { + this.minecraft.player.displayClientMessage(Component.literal("未找到该供应器的位置信息,无法打开UI"), true); + } + return; + } + + // PatternProviderInfo record: pos(), face(), playerWorld() + Object pos = info.getClass().getMethod("pos").invoke(info); + Object face = info.getClass().getMethod("face").invoke(info); // 可能为 null(方块型供应器) + Object playerWorld = info.getClass().getMethod("playerWorld").invoke(info); + + long posLong = (long) pos.getClass().getMethod("asLong").invoke(pos); + Object rl = playerWorld.getClass().getMethod("location").invoke(playerWorld); // ResourceLocation + String dimStr = (String) rl.getClass().getMethod("toString").invoke(rl); + int faceOrd = -1; + if (face != null) { + faceOrd = (int) face.getClass().getMethod("ordinal").invoke(face); + } + + // 发送 CGenericPacket("open_ui", [posLong, dim, face]) + try { + Class EPPNetworkHandlerClass = Class.forName("com.glodblock.github.extendedae.network.EPPNetworkHandler"); + Object handlerInstance = EPPNetworkHandlerClass.getField("INSTANCE").get(null); + + Class packetClass = Class.forName("com.glodblock.github.glodium.network.packet.CGenericPacket"); + Constructor constructor = packetClass.getConstructor(String.class, Object[].class); + Object packet = constructor.newInstance("open_ui", new Object[]{posLong, dimStr, faceOrd}); + + Class iMessage = Class.forName("com.glodblock.github.glodium.network.packet.IMessage"); + Method sendToServer = EPPNetworkHandlerClass.getMethod("sendToServer", iMessage); + + sendToServer.invoke(handlerInstance, packet); + if (this.minecraft != null && this.minecraft.player != null) { + EAP_LOGGER.debug("[EPlus] Sent open_ui packet: pos={}, dim={}, face={}", posLong, dimStr, faceOrd); + } + } catch (Throwable t) { + if (this.minecraft != null && this.minecraft.player != null) { + this.minecraft.player.displayClientMessage(Component.literal("❌ ExtendedAE Plus: 网络模块不可用,无法发送打开UI请求"), true); + } + } + } catch (Throwable t) { + EAP_LOGGER.warn("[EPlus] eap$tryOpenProviderUI failed: {}", t.toString()); + } + } + /** * 重置当前选择的样板供应器ID */ @@ -228,6 +332,13 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.addToLeftToolbar(this.eap$toggleSlotsButton); } + @Inject(method = "init", at = @At("TAIL"), remap = false) + private void eap$onInit(CallbackInfo ci) { + // 清理旧的打开UI按钮 + this.eap$openUIButtons.values().forEach(this::removeWidget); + this.eap$openUIButtons.clear(); + } + @Inject(method = "refreshList", at = @At("HEAD"), remap = false) private void onRefreshListStart(CallbackInfo ci) { // 更新按钮图标 @@ -236,6 +347,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.eap$showSlots ? "gui.expatternprovider.hide_slots" : "gui.expatternprovider.show_slots" ))); } + // 清理旧的打开UI按钮 + this.eap$openUIButtons.values().forEach(this::removeWidget); + this.eap$openUIButtons.clear(); } @Inject(method = "refreshList", at = @At("TAIL"), remap = false) @@ -342,6 +456,71 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen @Inject(method = "drawFG", at = @At("TAIL"), remap = false) private void eap$afterDrawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY, CallbackInfo ci) { + // 动态放置/创建每个组标题后的“打开UI”按钮 + try { + // 使用 Accessor 获取必要的字段,避免反射失败 + com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc = + (com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this; + java.util.ArrayList rows = acc.getRows(); + int currentScroll = acc.getScrollbar().getCurrentScroll(); + + // 直接引用目标类以获取其静态常量 + Class cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class; + int GUI_PADDING_X = getIntConst(cls, "GUI_PADDING_X", 22); + int GUI_PADDING_Y = getIntConst(cls, "GUI_PADDING_Y", 6); + int GUI_HEADER_HEIGHT = getIntConst(cls, "GUI_HEADER_HEIGHT", 51); + int ROW_HEIGHT = getIntConst(cls, "ROW_HEIGHT", 18); + int TEXT_MAX_WIDTH = getIntConst(cls, "TEXT_MAX_WIDTH", 155); + + int visibleRows = acc.getVisibleRows(); + + if (!eap$debugLoggedOnce) { + EAP_LOGGER.info("[EPlus] GuiExPatternTerminalMixin.afterDrawFG fired: rows={}, currentScroll={}, visibleRows={}", + rows.size(), currentScroll, visibleRows); + eap$debugLoggedOnce = true; + } + + // 先隐藏旧按钮,避免残留 + for (Button b : this.eap$openUIButtons.values()) { + b.visible = false; + } + + int shownCount = 0; + for (int i = 0; i < visibleRows; i++) { + int rowIndex = currentScroll + i; + if (rowIndex < 0 || rowIndex >= rows.size()) { + continue; + } + Object row = rows.get(rowIndex); + if (!row.getClass().getSimpleName().equals("GroupHeaderRow")) { + continue; + } + + // 放置按钮:位于名称文本右侧,与原类 choiceButton 锚点相邻,向右偏移 20px + int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 40; + int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT; + + Button btn = eap$openUIButtons.get(rowIndex); + if (btn == null) { + btn = Button.builder(Component.literal("UI"), (b) -> { + eap$tryOpenProviderUI(rowIndex); + }).size(18, 16).build(); + btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面"))); + eap$openUIButtons.put(rowIndex, btn); + this.addRenderableWidget(btn); + } + btn.setPosition(bx, by); + btn.visible = true; + shownCount++; + } + if (shownCount == 0) { + EAP_LOGGER.debug("[EPlus] No GroupHeaderRow visible in current page (scroll={}, rows={})", currentScroll, rows.size()); + } else { + EAP_LOGGER.debug("[EPlus] GroupHeaderRow buttons shown count: {}", shownCount); + } + } catch (Throwable ignored) { + } + // 原有的搜索高亮逻辑 // 仅当任一搜索框非空时绘制叠加层(与原版行为保持一致) boolean searchActive = (this.searchOutField != null && !this.searchOutField.getValue().isEmpty()) From f1909d7acd63cb55911ab5009385b6032e88d34f Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:16:07 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E4=B8=BA=E7=AE=A1=E7=90=86=E7=BB=88?= =?UTF-8?q?=E7=AB=AF=E5=A2=9E=E5=8A=A0=E6=89=93=E5=BC=80=E6=9C=BA=E5=99=A8?= =?UTF-8?q?ui=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixin/extendedae/GuiExPatternTerminalMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index 0a1cf72..dbcf59a 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -504,7 +504,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen if (btn == null) { btn = Button.builder(Component.literal("UI"), (b) -> { eap$tryOpenProviderUI(rowIndex); - }).size(18, 16).build(); + }).size(14, 12).build(); btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面"))); eap$openUIButtons.put(rowIndex, btn); this.addRenderableWidget(btn); From 204efdf3ce29e15aae1ee4db651e0d874d5475c5 Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:12:08 +0800 Subject: [PATCH 03/10] 1 --- .../mixin/accessor/ScreenInvoker.java | 14 -------------- .../extendedae/GuiExPatternTerminalMixin.java | 5 +++-- src/main/resources/extendedae_plus.mixins.json | 1 - 3 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/com/extendedae_plus/mixin/accessor/ScreenInvoker.java diff --git a/src/main/java/com/extendedae_plus/mixin/accessor/ScreenInvoker.java b/src/main/java/com/extendedae_plus/mixin/accessor/ScreenInvoker.java deleted file mode 100644 index c6edc7a..0000000 --- a/src/main/java/com/extendedae_plus/mixin/accessor/ScreenInvoker.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.extendedae_plus.mixin.accessor; - -import net.minecraft.client.gui.components.Renderable; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraft.client.gui.screens.Screen; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(Screen.class) -public interface ScreenInvoker { - @Invoker("addRenderableWidget") - W eap$invokeAddRenderableWidget(W widget); -} diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index dbcf59a..b18567a 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -36,7 +36,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @Pseudo -@Mixin(value = GuiExPatternTerminal.class, remap = false) +@Mixin(value = GuiExPatternTerminal.class) public abstract class GuiExPatternTerminalMixin extends AEBaseScreen { @Unique @@ -122,8 +122,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen /** * 拦截鼠标点击事件,实现Shift+左键快速上传样板功能 + * 注意:某些整合包的 ExtendedAE 版本不在该类中覆写 mouseClicked,此处设置 require=0 以防止注入失败导致崩溃。 */ - @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true, require = 0) private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { // 检查是否是左键点击 + Shift键 if (button == 0 && hasShiftDown()) { diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 9e0c12e..e515bd3 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -7,7 +7,6 @@ "PickFromWirelessMixin", "accessor.AbstractContainerScreenAccessor", "accessor.ScreenAccessor", - "accessor.ScreenInvoker", "ae2.AEBaseScreenMixin", "ae2.PatternEncodingTermScreenMixin", "ae2.PatternProviderScreenMixin", From 5995b7bcd9b6f8bd7a062a26f64906855bc1f4d4 Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:31:06 +0800 Subject: [PATCH 04/10] 1 --- .../mixin/extendedae/GuiExPatternTerminalMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index b18567a..218603d 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -333,7 +333,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.addToLeftToolbar(this.eap$toggleSlotsButton); } - @Inject(method = "init", at = @At("TAIL"), remap = false) + @Inject(method = "init", at = @At("TAIL"), remap = false, require = 0) private void eap$onInit(CallbackInfo ci) { // 清理旧的打开UI按钮 this.eap$openUIButtons.values().forEach(this::removeWidget); From 53945a8247b5f1a125b2279238c644a5952613d0 Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:40:13 +0800 Subject: [PATCH 05/10] 2 --- .../mixin/extendedae/GuiExPatternTerminalMixin.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index 218603d..b4d33dc 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -10,6 +10,8 @@ import appeng.client.gui.widgets.AETextField; import appeng.client.gui.widgets.IconButton; import appeng.menu.AEBaseMenu; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; @@ -18,6 +20,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; @@ -252,12 +256,12 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen Object face = info.getClass().getMethod("face").invoke(info); // 可能为 null(方块型供应器) Object playerWorld = info.getClass().getMethod("playerWorld").invoke(info); - long posLong = (long) pos.getClass().getMethod("asLong").invoke(pos); - Object rl = playerWorld.getClass().getMethod("location").invoke(playerWorld); // ResourceLocation - String dimStr = (String) rl.getClass().getMethod("toString").invoke(rl); + // 避免对 MC 类进行反射,使用强制类型转换后直接调用方法(由 Forge 运行时重映射保证) + long posLong = ((BlockPos) pos).asLong(); + String dimStr = ((ResourceKey) playerWorld).location().toString(); int faceOrd = -1; if (face != null) { - faceOrd = (int) face.getClass().getMethod("ordinal").invoke(face); + faceOrd = ((Direction) face).ordinal(); } // 发送 CGenericPacket("open_ui", [posLong, dim, face]) From 1d193d16742777f05d9379534333477a7bde7942 Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:25:34 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E6=94=B9=E7=94=A8=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E7=9A=84=E7=BD=91=E7=BB=9C=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extendedae/GuiExPatternTerminalMixin.java | 28 ++-- .../extendedae_plus/network/ModNetwork.java | 6 + .../network/OpenProviderUiC2SPacket.java | 149 ++++++++++++++++++ 3 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index b4d33dc..5607b3e 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -10,6 +10,8 @@ import appeng.client.gui.widgets.AETextField; import appeng.client.gui.widgets.IconButton; import appeng.menu.AEBaseMenu; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; +import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.network.OpenProviderUiC2SPacket; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.client.gui.GuiGraphics; @@ -22,6 +24,7 @@ import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; +import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; @@ -264,25 +267,22 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen faceOrd = ((Direction) face).ordinal(); } - // 发送 CGenericPacket("open_ui", [posLong, dim, face]) + // 发送我们自己的 C2S 包:OpenProviderUiC2SPacket try { - Class EPPNetworkHandlerClass = Class.forName("com.glodblock.github.extendedae.network.EPPNetworkHandler"); - Object handlerInstance = EPPNetworkHandlerClass.getField("INSTANCE").get(null); - - Class packetClass = Class.forName("com.glodblock.github.glodium.network.packet.CGenericPacket"); - Constructor constructor = packetClass.getConstructor(String.class, Object[].class); - Object packet = constructor.newInstance("open_ui", new Object[]{posLong, dimStr, faceOrd}); - - Class iMessage = Class.forName("com.glodblock.github.glodium.network.packet.IMessage"); - Method sendToServer = EPPNetworkHandlerClass.getMethod("sendToServer", iMessage); - - sendToServer.invoke(handlerInstance, packet); if (this.minecraft != null && this.minecraft.player != null) { - EAP_LOGGER.debug("[EPlus] Sent open_ui packet: pos={}, dim={}, face={}", posLong, dimStr, faceOrd); + this.minecraft.player.displayClientMessage(Component.literal("↗ 正在请求打开供应器界面..."), true); + } + ModNetwork.CHANNEL.sendToServer(new OpenProviderUiC2SPacket( + posLong, + new ResourceLocation(dimStr), + faceOrd + )); + if (this.minecraft != null && this.minecraft.player != null) { + EAP_LOGGER.info("[EPlus] Sent OpenProviderUiC2SPacket: pos={}, dim={}, face={}", posLong, dimStr, faceOrd); } } catch (Throwable t) { if (this.minecraft != null && this.minecraft.player != null) { - this.minecraft.player.displayClientMessage(Component.literal("❌ ExtendedAE Plus: 网络模块不可用,无法发送打开UI请求"), true); + this.minecraft.player.displayClientMessage(Component.literal("❌ ExtendedAE Plus: 发送打开UI请求失败"), true); } } } catch (Throwable t) { diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/network/ModNetwork.java index 3aad8ea..f057ba1 100644 --- a/src/main/java/com/extendedae_plus/network/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/network/ModNetwork.java @@ -18,6 +18,12 @@ public class ModNetwork { private static int id = 0; public static void register() { + CHANNEL.messageBuilder(OpenProviderUiC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(OpenProviderUiC2SPacket::encode) + .decoder(OpenProviderUiC2SPacket::decode) + .consumerNetworkThread(OpenProviderUiC2SPacket::handle) + .add(); + CHANNEL.messageBuilder(PickFromWirelessC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) .encoder(PickFromWirelessC2SPacket::encode) .decoder(PickFromWirelessC2SPacket::decode) diff --git a/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java b/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java new file mode 100644 index 0000000..2ee4a18 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java @@ -0,0 +1,149 @@ +package com.extendedae_plus.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.InteractionResult; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.Level; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkHooks; + +import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class OpenProviderUiC2SPacket { + private final long posLong; + private final ResourceLocation dimId; + private final int faceOrd; // 目前保留,若目标需要可用 + + public OpenProviderUiC2SPacket(long posLong, ResourceLocation dimId, int faceOrd) { + this.posLong = posLong; + this.dimId = dimId; + this.faceOrd = faceOrd; + } + + public static void encode(OpenProviderUiC2SPacket msg, FriendlyByteBuf buf) { + buf.writeLong(msg.posLong); + buf.writeResourceLocation(msg.dimId); + buf.writeVarInt(msg.faceOrd); + } + + public static OpenProviderUiC2SPacket decode(FriendlyByteBuf buf) { + long posLong = buf.readLong(); + ResourceLocation dimId = buf.readResourceLocation(); + int faceOrd = buf.readVarInt(); + return new OpenProviderUiC2SPacket(posLong, dimId, faceOrd); + + } + + public static void handle(OpenProviderUiC2SPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player == null) return; + Logger logger = LogManager.getLogger("ExtendedAE_Plus"); + + // 校验维度与方块 + ResourceKey levelKey = ResourceKey.create(Registries.DIMENSION, msg.dimId); + ServerLevel level = player.server.getLevel(levelKey); + if (level == null) { + logger.warn("[EPlus] OpenProviderUiC2SPacket: invalid dimension {}", msg.dimId); + player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 维度无效:" + msg.dimId), true); + return; // 无效维度 + } + + BlockPos pos = BlockPos.of(msg.posLong); + if (!level.isLoaded(pos)) { + logger.warn("[EPlus] OpenProviderUiC2SPacket: chunk not loaded at {} in {}", pos, msg.dimId); + player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 区块未加载:" + pos.toShortString()), true); + return; // 区块未加载 + } + + var be = level.getBlockEntity(pos); + var stateAtPos = level.getBlockState(pos); + + // 目标通常是供应器所面对/连接的“相邻方块”,优先尝试邻居 + Direction[] tries = (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length) + ? new Direction[]{Direction.values()[msg.faceOrd]} + : Direction.values(); + + for (Direction dir : tries) { + BlockPos targetPos = pos.relative(dir); + BlockEntity tbe = level.getBlockEntity(targetPos); + if (tbe instanceof MenuProvider provider) { + NetworkHooks.openScreen(player, provider, targetPos); + logger.debug("[EPlus] OpenProviderUiC2SPacket: opened BE MenuProvider at {} (neighbor via {})", targetPos, dir); + return; + } + var tstate = level.getBlockState(targetPos); + MenuProvider provider2 = tstate.getMenuProvider(level, targetPos); + if (provider2 != null) { + NetworkHooks.openScreen(player, provider2, targetPos); + logger.debug("[EPlus] OpenProviderUiC2SPacket: opened State MenuProvider at {} (neighbor via {})", targetPos, dir); + return; + } + } + + // 如果邻居也未提供 MenuProvider,则兜底:尽量模拟一次徒手右键相邻方块 + boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty(); + if (anyHandEmpty) { + InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; + if (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length) { + Direction dir = Direction.values()[msg.faceOrd]; + BlockPos targetPos = pos.relative(dir); + var state2 = level.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false); + InteractionResult r = state2.use(level, player, hand, hit); + logger.debug("[EPlus] OpenProviderUiC2SPacket: fallback(use) at {} hit {} (via {}), result={}", targetPos, dir.getOpposite(), dir, r); + if (r.consumesAction()) { + player.displayClientMessage(net.minecraft.network.chat.Component.literal("✅ 已尝试模拟右键交互: " + r), true); + return; + } + } else { + // 无明确朝向:优先挑选有方块实体的邻居,否则挑选非空气方块 + Direction chosen = null; + for (Direction d : Direction.values()) { + if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; } + } + if (chosen == null) { + for (Direction d : Direction.values()) { + if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; } + } + } + if (chosen != null) { + BlockPos targetPos = pos.relative(chosen); + var state2 = level.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); + InteractionResult r = state2.use(level, player, hand, hit); + logger.debug("[EPlus] OpenProviderUiC2SPacket: fallback(use) at {} hit {} (auto via {}), result={}", targetPos, chosen.getOpposite(), chosen, r); + if (r.consumesAction()) { + player.displayClientMessage(net.minecraft.network.chat.Component.literal("✅ 已尝试模拟右键交互: " + r), true); + return; + } + } else { + logger.debug("[EPlus] OpenProviderUiC2SPacket: no neighbor candidate for fallback (faceOrd<0)"); + } + } + } else { + logger.debug("[EPlus] OpenProviderUiC2SPacket: skip fallback(use) because both hands occupied"); + } + + // 若走到这里,说明未能打开界面 + logger.warn("[EPlus] OpenProviderUiC2SPacket: No MenuProvider around {} (BE={}, Block={})", pos, + be == null ? "null" : be.getClass().getName(), stateAtPos.getBlock().getClass().getName()); + player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 未找到可打开的相邻界面"), true); + }); + context.setPacketHandled(true); + } +} From 846cc7d02da76afefb1a210178f0bb9e67c78796 Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:43:23 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E4=BB=A5=E5=8F=8A=E5=B0=BA=E5=AF=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extendedae/GuiExPatternTerminalMixin.java | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index 5607b3e..5fb3270 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -17,6 +17,7 @@ import net.minecraft.core.Direction; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Rect2i; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; @@ -337,6 +338,63 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.addToLeftToolbar(this.eap$toggleSlotsButton); } + /** + * 处理屏幕缩放(resize)后按钮位置未更新的问题: + * - 清理并移除现有的“打开UI”按钮 + * - 尝试重置滚动条并刷新列表 + * 缩放后的下一帧,drawFG 会基于新的 leftPos/topPos 重建与定位按钮 + */ + @Inject(method = "resize", at = @At("TAIL"), remap = false, require = 0) + private void eap$onResize(Minecraft mc, int width, int height, CallbackInfo ci) { + try { + // 移除并清理按钮,避免旧位置残留 + this.eap$openUIButtons.values().forEach(this::removeWidget); + this.eap$openUIButtons.clear(); + + // 重置一次滚动条,避免可见行/偏移在缩放后与 UI 尺寸不一致 + try { + Method resetScrollbarMethod = null; + try { + resetScrollbarMethod = this.getClass().getDeclaredMethod("resetScrollbar"); + } catch (NoSuchMethodException e1) { + try { + resetScrollbarMethod = this.getClass().getSuperclass().getDeclaredMethod("resetScrollbar"); + } catch (NoSuchMethodException e2) { + resetScrollbarMethod = null; + } + } + if (resetScrollbarMethod != null) { + resetScrollbarMethod.setAccessible(true); + resetScrollbarMethod.invoke(this); + } + } catch (Throwable ignored) { + } + + // 刷新列表,使 rows/visibleRows 立即以新尺寸重算 + try { + Method refreshMethod = null; + try { + refreshMethod = this.getClass().getDeclaredMethod("refreshList"); + } catch (NoSuchMethodException e1) { + try { + refreshMethod = this.getClass().getSuperclass().getDeclaredMethod("refreshList"); + } catch (NoSuchMethodException e2) { + refreshMethod = null; + } + } + if (refreshMethod != null) { + refreshMethod.setAccessible(true); + refreshMethod.invoke(this); + } + } catch (Throwable ignored) { + } + + // 下次绘制重新输出一次调试行,便于确认缩放后的 rows/scroll + this.eap$debugLoggedOnce = false; + } catch (Throwable ignored) { + } + } + @Inject(method = "init", at = @At("TAIL"), remap = false, require = 0) private void eap$onInit(CallbackInfo ci) { // 清理旧的打开UI按钮 @@ -503,7 +561,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen // 放置按钮:位于名称文本右侧,与原类 choiceButton 锚点相邻,向右偏移 20px int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 40; - int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT; + int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT - 3; Button btn = eap$openUIButtons.get(rowIndex); if (btn == null) { From b2b6b24231a883694faf792f9c674d0f65c4d15c Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:14:30 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E5=88=A0=E9=99=A4=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extendedae/GuiExPatternTerminalMixin.java | 24 ++++--------------- .../network/OpenProviderUiC2SPacket.java | 24 ++++--------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java index 5fb3270..94655ab 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/GuiExPatternTerminalMixin.java @@ -270,24 +270,16 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen // 发送我们自己的 C2S 包:OpenProviderUiC2SPacket try { - if (this.minecraft != null && this.minecraft.player != null) { - this.minecraft.player.displayClientMessage(Component.literal("↗ 正在请求打开供应器界面..."), true); - } ModNetwork.CHANNEL.sendToServer(new OpenProviderUiC2SPacket( posLong, new ResourceLocation(dimStr), faceOrd )); - if (this.minecraft != null && this.minecraft.player != null) { - EAP_LOGGER.info("[EPlus] Sent OpenProviderUiC2SPacket: pos={}, dim={}, face={}", posLong, dimStr, faceOrd); - } } catch (Throwable t) { - if (this.minecraft != null && this.minecraft.player != null) { - this.minecraft.player.displayClientMessage(Component.literal("❌ ExtendedAE Plus: 发送打开UI请求失败"), true); - } + // 静默失败:不提示玩家 } } catch (Throwable t) { - EAP_LOGGER.warn("[EPlus] eap$tryOpenProviderUI failed: {}", t.toString()); + // 静默失败:不输出日志 } } @@ -537,11 +529,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen int visibleRows = acc.getVisibleRows(); - if (!eap$debugLoggedOnce) { - EAP_LOGGER.info("[EPlus] GuiExPatternTerminalMixin.afterDrawFG fired: rows={}, currentScroll={}, visibleRows={}", - rows.size(), currentScroll, visibleRows); - eap$debugLoggedOnce = true; - } + // 生产环境移除调试日志 // 先隐藏旧按钮,避免残留 for (Button b : this.eap$openUIButtons.values()) { @@ -576,11 +564,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen btn.visible = true; shownCount++; } - if (shownCount == 0) { - EAP_LOGGER.debug("[EPlus] No GroupHeaderRow visible in current page (scroll={}, rows={})", currentScroll, rows.size()); - } else { - EAP_LOGGER.debug("[EPlus] GroupHeaderRow buttons shown count: {}", shownCount); - } + // 生产环境移除调试日志 } catch (Throwable ignored) { } diff --git a/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java b/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java index 2ee4a18..8a02170 100644 --- a/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java @@ -19,8 +19,6 @@ import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkHooks; import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; public class OpenProviderUiC2SPacket { private final long posLong; @@ -52,21 +50,17 @@ public class OpenProviderUiC2SPacket { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); if (player == null) return; - Logger logger = LogManager.getLogger("ExtendedAE_Plus"); + // 校验维度与方块 ResourceKey levelKey = ResourceKey.create(Registries.DIMENSION, msg.dimId); ServerLevel level = player.server.getLevel(levelKey); if (level == null) { - logger.warn("[EPlus] OpenProviderUiC2SPacket: invalid dimension {}", msg.dimId); - player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 维度无效:" + msg.dimId), true); return; // 无效维度 } BlockPos pos = BlockPos.of(msg.posLong); if (!level.isLoaded(pos)) { - logger.warn("[EPlus] OpenProviderUiC2SPacket: chunk not loaded at {} in {}", pos, msg.dimId); - player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 区块未加载:" + pos.toShortString()), true); return; // 区块未加载 } @@ -83,14 +77,12 @@ public class OpenProviderUiC2SPacket { BlockEntity tbe = level.getBlockEntity(targetPos); if (tbe instanceof MenuProvider provider) { NetworkHooks.openScreen(player, provider, targetPos); - logger.debug("[EPlus] OpenProviderUiC2SPacket: opened BE MenuProvider at {} (neighbor via {})", targetPos, dir); return; } var tstate = level.getBlockState(targetPos); MenuProvider provider2 = tstate.getMenuProvider(level, targetPos); if (provider2 != null) { NetworkHooks.openScreen(player, provider2, targetPos); - logger.debug("[EPlus] OpenProviderUiC2SPacket: opened State MenuProvider at {} (neighbor via {})", targetPos, dir); return; } } @@ -105,9 +97,7 @@ public class OpenProviderUiC2SPacket { var state2 = level.getBlockState(targetPos); var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false); InteractionResult r = state2.use(level, player, hand, hit); - logger.debug("[EPlus] OpenProviderUiC2SPacket: fallback(use) at {} hit {} (via {}), result={}", targetPos, dir.getOpposite(), dir, r); if (r.consumesAction()) { - player.displayClientMessage(net.minecraft.network.chat.Component.literal("✅ 已尝试模拟右键交互: " + r), true); return; } } else { @@ -126,24 +116,18 @@ public class OpenProviderUiC2SPacket { var state2 = level.getBlockState(targetPos); var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); InteractionResult r = state2.use(level, player, hand, hit); - logger.debug("[EPlus] OpenProviderUiC2SPacket: fallback(use) at {} hit {} (auto via {}), result={}", targetPos, chosen.getOpposite(), chosen, r); if (r.consumesAction()) { - player.displayClientMessage(net.minecraft.network.chat.Component.literal("✅ 已尝试模拟右键交互: " + r), true); return; } } else { - logger.debug("[EPlus] OpenProviderUiC2SPacket: no neighbor candidate for fallback (faceOrd<0)"); + // 无可选邻居 } } } else { - logger.debug("[EPlus] OpenProviderUiC2SPacket: skip fallback(use) because both hands occupied"); + // 双手占用则跳过兜底交互 } - // 若走到这里,说明未能打开界面 - logger.warn("[EPlus] OpenProviderUiC2SPacket: No MenuProvider around {} (BE={}, Block={})", pos, - be == null ? "null" : be.getClass().getName(), stateAtPos.getBlock().getClass().getName()); - player.displayClientMessage(net.minecraft.network.chat.Component.literal("❌ 未找到可打开的相邻界面"), true); + context.setPacketHandled(true); }); - context.setPacketHandled(true); } } From 3540c52676c77ea92021c9e8d75432471a5ed12f Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:36:18 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E6=88=90?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E7=95=8C=E9=9D=A2shift=E5=B7=A6=E9=94=AE?= =?UTF-8?q?=E6=89=93=E5=BC=80=E6=9C=BA=E5=99=A8ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixin/ae2/AEBaseScreenMixin.java | 41 +++++ .../ae2/accessor/CraftingCPUMenuAccessor.java | 12 ++ .../network/CraftingMonitorJumpC2SPacket.java | 156 ++++++++++++++++++ .../extendedae_plus/network/ModNetwork.java | 6 + .../resources/extendedae_plus.mixins.json | 1 + 5 files changed, 216 insertions(+) create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java create mode 100644 src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java index 17d1ce8..d6cbb04 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java @@ -2,21 +2,28 @@ package com.extendedae_plus.mixin.ae2; import appeng.client.Point; import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.StackWithBounds; +import appeng.client.gui.me.crafting.CraftingCPUScreen; import appeng.client.gui.TextOverride; import appeng.client.gui.style.PaletteColor; import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.Text; import appeng.client.gui.style.TextAlignment; +import appeng.api.stacks.AEKey; import appeng.menu.slot.AppEngSlot; import com.extendedae_plus.api.ExPatternPageAccessor; +import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket; +import com.extendedae_plus.network.ModNetwork; import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; +import com.mojang.logging.LogUtils; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.Rect2i; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.world.inventory.Slot; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -38,6 +45,40 @@ public abstract class AEBaseScreenMixin { return null; } + /** + * 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+左键, + * 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket。 + */ + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) + private void eap$craftingCpuShiftLeftClick(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + // 仅处理 CraftingCPUScreen 实例 + Object self = this; + if (!(self instanceof CraftingCPUScreen screen)) { + return; + } + // 仅在 Shift + 左键 时触发 + if (button != 0 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) { + return; + } + try { + StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY); + if (hovered == null || hovered.stack() == null) { + return; + } + AEKey key = hovered.stack().what(); + if (key == null) { + return; + } + // Debug: 标记一次发送 + try { + LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key); + } catch (Throwable ignored2) {} + ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key)); + cir.setReturnValue(true); + } catch (Throwable ignored) { + } + } + @Unique private static int eap$getIntField(Object self, String name, int def) { Class c = self.getClass(); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java new file mode 100644 index 0000000..96684a6 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.mixin.ae2.accessor; + +import appeng.api.networking.IGrid; +import appeng.menu.me.crafting.CraftingCPUMenu; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CraftingCPUMenu.class) +public interface CraftingCPUMenuAccessor { + @Accessor("grid") + IGrid getGrid(); +} diff --git a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java new file mode 100644 index 0000000..6211f57 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java @@ -0,0 +1,156 @@ +package com.extendedae_plus.network; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.networking.IGrid; +import appeng.api.networking.crafting.ICraftingProvider; +import appeng.api.networking.security.IActionHost; +import appeng.api.stacks.AEKey; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.me.service.CraftingService; +import appeng.menu.me.crafting.CraftingCPUMenu; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; +import com.extendedae_plus.mixin.ae2.accessor.CraftingCPUMenuAccessor; +import com.mojang.logging.LogUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkHooks; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。 + * 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器, + * 尝试打开其目标机器的 GUI。 + */ +public class CraftingMonitorJumpC2SPacket { + private final AEKey what; + + public CraftingMonitorJumpC2SPacket(AEKey what) { + this.what = what; + } + + public static void encode(CraftingMonitorJumpC2SPacket msg, FriendlyByteBuf buf) { + AEKey.writeKey(buf, msg.what); + } + + public static CraftingMonitorJumpC2SPacket decode(FriendlyByteBuf buf) { + AEKey key = AEKey.readKey(buf); + return new CraftingMonitorJumpC2SPacket(key); + } + + public static void handle(CraftingMonitorJumpC2SPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player == null) return; + + LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName()); + + // 必须在 CraftingCPU 界面内 + if (!(player.containerMenu instanceof CraftingCPUMenu menu)) { + LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort"); + return; + } + // 直接通过 accessor 从菜单获取 Grid,避免对方块实体/level 的依赖 + IGrid grid = ((CraftingCPUMenuAccessor) menu).getGrid(); + if (grid == null) { + LogUtils.getLogger().info("EAP[S]: grid is null, abort"); + return; + } + + var cs = grid.getCraftingService(); + if (!(cs instanceof CraftingService craftingService)) { + LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort"); + return; + } + + // 1) 根据 AEKey 找到可能的样板(pattern) + Collection patterns = craftingService.getCraftingFor(msg.what); + LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what); + if (patterns.isEmpty()) { + return; + } + + // 2) 遍历提供该样板的 Provider,优先 PatternProviderLogic + for (var pattern : patterns) { + var providers = craftingService.getProviders(pattern); + int providerCount = 0; + for (var provider : providers) { + providerCount++; + try { + LogUtils.getLogger().info("EAP[S]: provider class={}", provider.getClass().getName()); + } catch (Throwable ignored) {} + if (provider instanceof PatternProviderLogic ppl) { + // 使用 accessor 获取 host(受保护字段通过 accessor 访问) + PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); + if (host == null) continue; + var pbe = host.getBlockEntity(); + var level = pbe.getLevel(); + if (!(level instanceof ServerLevel serverLevel)) continue; + + // 尝试对邻居打开 GUI(复用 OpenProviderUiC2SPacket 的策略) + for (Direction dir : host.getTargets()) { + BlockPos targetPos = pbe.getBlockPos().relative(dir); + var tbe = serverLevel.getBlockEntity(targetPos); + if (tbe instanceof MenuProvider provider1) { + LogUtils.getLogger().info("EAP[S]: open screen via MenuProvider at {}", targetPos); + NetworkHooks.openScreen(player, provider1, targetPos); + context.setPacketHandled(true); + return; + } + var tstate = serverLevel.getBlockState(targetPos); + var provider2 = tstate.getMenuProvider(serverLevel, targetPos); + if (provider2 != null) { + LogUtils.getLogger().info("EAP[S]: open screen via state.getMenuProvider at {}", targetPos); + NetworkHooks.openScreen(player, provider2, targetPos); + context.setPacketHandled(true); + return; + } + } + + // 兜底:若无 MenuProvider,模拟徒手右键一次(优先有方块实体的面) + boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty(); + if (anyHandEmpty) { + InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; + Direction chosen = null; + for (Direction d : host.getTargets()) { + if (serverLevel.getBlockEntity(pbe.getBlockPos().relative(d)) != null) { chosen = d; break; } + } + if (chosen == null) { + for (Direction d : host.getTargets()) { + if (!serverLevel.getBlockState(pbe.getBlockPos().relative(d)).isAir()) { chosen = d; break; } + } + } + if (chosen != null) { + BlockPos targetPos = pbe.getBlockPos().relative(chosen); + var state2 = serverLevel.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); + InteractionResult r = state2.use(serverLevel, player, hand, hit); + if (r.consumesAction()) { + LogUtils.getLogger().info("EAP[S]: opened via simulated use at {} ({}), result={}", targetPos, chosen, r); + context.setPacketHandled(true); + return; + } + } + } + } + } + LogUtils.getLogger().info("EAP[S]: providers count for one pattern: {}", providerCount); + } + LogUtils.getLogger().info("EAP[S]: no target opened for key={}", msg.what); + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/network/ModNetwork.java index f057ba1..cc50a47 100644 --- a/src/main/java/com/extendedae_plus/network/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/network/ModNetwork.java @@ -71,6 +71,12 @@ public class ModNetwork { .decoder(AdvancedBlockingSyncS2CPacket::decode) .consumerNetworkThread(AdvancedBlockingSyncS2CPacket::handle) .add(); + + CHANNEL.messageBuilder(CraftingMonitorJumpC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(CraftingMonitorJumpC2SPacket::encode) + .decoder(CraftingMonitorJumpC2SPacket::decode) + .consumerNetworkThread(CraftingMonitorJumpC2SPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index e515bd3..f66260c 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -31,6 +31,7 @@ "ae2.PatternEncodingTermMenuMixin", "ae2.PatternProviderLogicAdvancedMixin", "ae2.PatternProviderMenuAdvancedMixin", + "ae2.accessor.CraftingCPUMenuAccessor", "ae2.accessor.MEStorageMenuAccessor", "ae2.accessor.PatternEncodingTermMenuAccessor", "ae2.accessor.PatternProviderLogicAccessor", From a75d7fbc5c24053e24af02830952f43358e7f04f Mon Sep 17 00:00:00 2001 From: GaLi <133291877+GaLicn@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:19:55 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E6=88=90?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E7=95=8C=E9=9D=A2shift=E5=B7=A6=E9=94=AE?= =?UTF-8?q?=E6=89=93=E5=BC=80=E6=9C=BA=E5=99=A8ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EPlusCraftingCubeModelProvider.java | 6 +- .../mixin/ae2/AEBaseScreenMixin.java | 35 ++++++ .../ae2/accessor/CraftingCPUMenuAccessor.java | 12 -- .../network/CraftingMonitorJumpC2SPacket.java | 55 ++++----- .../CraftingMonitorOpenProviderC2SPacket.java | 115 ++++++++++++++++++ .../extendedae_plus/network/ModNetwork.java | 6 + .../resources/extendedae_plus.mixins.json | 1 - 7 files changed, 186 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java create mode 100644 src/main/java/com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java diff --git a/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java b/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java index 3e98d51..850836a 100644 --- a/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java +++ b/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.function.Function; /** - * 参照 MAE2 的 DynamicCraftingCubeModelProvider,实现形成态光照模型。 + * 形成态光照模型。 */ public class EPlusCraftingCubeModelProvider extends AbstractCraftingUnitModelProvider { @@ -29,13 +29,13 @@ public class EPlusCraftingCubeModelProvider public static final ChunkRenderTypeSet CUTOUT = ChunkRenderTypeSet.of(RenderType.cutout()); private static final List MATERIALS = new ArrayList<>(); - // 与 MAE2 一致:将环形边框与基础发光底图放在本模组命名空间 + //将环形边框与基础发光底图放在本模组命名空间 protected static final Material RING_CORNER = texture(ExtendedAEPlus.MODID, "ring_corner"); protected static final Material RING_SIDE_HOR = texture(ExtendedAEPlus.MODID, "ring_side_hor"); protected static final Material RING_SIDE_VER = texture(ExtendedAEPlus.MODID, "ring_side_ver"); protected static final Material LIGHT_BASE = texture(ExtendedAEPlus.MODID, "light_base"); - // 我们自己的亮面贴图(formed 时使用) + // 亮面贴图(formed 时使用) protected static final Material ACCELERATOR_4X_LIGHT = texture(ExtendedAEPlus.MODID, "4x_accelerator_light"); protected static final Material ACCELERATOR_16X_LIGHT = texture(ExtendedAEPlus.MODID, diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java index d6cbb04..4194d24 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/AEBaseScreenMixin.java @@ -14,6 +14,7 @@ import appeng.menu.slot.AppEngSlot; import com.extendedae_plus.api.ExPatternPageAccessor; import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket; import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket; import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; import com.mojang.logging.LogUtils; @@ -79,6 +80,40 @@ public abstract class AEBaseScreenMixin { } } + /** + * 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+右键, + * 读取鼠标下的 AEKey 并发送 CraftingMonitorOpenProviderC2SPacket(打开样板供应器UI)。 + */ + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) + private void eap$craftingCpuShiftRightClick(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + // 仅处理 CraftingCPUScreen 实例 + Object self = this; + if (!(self instanceof CraftingCPUScreen screen)) { + return; + } + // 仅在 Shift + 右键 时触发 + if (button != 1 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) { + return; + } + try { + StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY); + if (hovered == null || hovered.stack() == null) { + return; + } + AEKey key = hovered.stack().what(); + if (key == null) { + return; + } + // Debug: 标记一次发送(打开供应器UI) + try { + LogUtils.getLogger().info("EAP: Send CraftingMonitorOpenProviderC2SPacket: {}", key); + } catch (Throwable ignored2) {} + ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key)); + cir.setReturnValue(true); + } catch (Throwable ignored) { + } + } + @Unique private static int eap$getIntField(Object self, String name, int def) { Class c = self.getClass(); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java deleted file mode 100644 index 96684a6..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/CraftingCPUMenuAccessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.extendedae_plus.mixin.ae2.accessor; - -import appeng.api.networking.IGrid; -import appeng.menu.me.crafting.CraftingCPUMenu; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(CraftingCPUMenu.class) -public interface CraftingCPUMenuAccessor { - @Accessor("grid") - IGrid getGrid(); -} diff --git a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java index 6211f57..eacd863 100644 --- a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java @@ -8,9 +8,7 @@ import appeng.api.stacks.AEKey; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.me.service.CraftingService; -import appeng.menu.me.crafting.CraftingCPUMenu; import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; -import com.extendedae_plus.mixin.ae2.accessor.CraftingCPUMenuAccessor; import com.mojang.logging.LogUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -59,12 +57,17 @@ public class CraftingMonitorJumpC2SPacket { LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName()); // 必须在 CraftingCPU 界面内 - if (!(player.containerMenu instanceof CraftingCPUMenu menu)) { + if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) { LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort"); return; } - // 直接通过 accessor 从菜单获取 Grid,避免对方块实体/level 的依赖 - IGrid grid = ((CraftingCPUMenuAccessor) menu).getGrid(); + + // 通过菜单 target(可能是 BlockEntity/Part/ItemHost)按 IActionHost 获取 Grid + IGrid grid = null; + Object target = ((appeng.menu.AEBaseMenu) menu).getTarget(); + if (target instanceof IActionHost host && host.getActionableNode() != null) { + grid = host.getActionableNode().getGrid(); + } if (grid == null) { LogUtils.getLogger().info("EAP[S]: grid is null, abort"); return; @@ -97,8 +100,7 @@ public class CraftingMonitorJumpC2SPacket { PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); if (host == null) continue; var pbe = host.getBlockEntity(); - var level = pbe.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) continue; + ServerLevel serverLevel = player.serverLevel(); // 尝试对邻居打开 GUI(复用 OpenProviderUiC2SPacket 的策略) for (Direction dir : host.getTargets()) { @@ -120,29 +122,26 @@ public class CraftingMonitorJumpC2SPacket { } } - // 兜底:若无 MenuProvider,模拟徒手右键一次(优先有方块实体的面) - boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty(); - if (anyHandEmpty) { - InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; - Direction chosen = null; + // 兜底:若无 MenuProvider,始终模拟一次右键(优先有方块实体的一面) + InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.MAIN_HAND; + Direction chosen = null; + for (Direction d : host.getTargets()) { + if (serverLevel.getBlockEntity(pbe.getBlockPos().relative(d)) != null) { chosen = d; break; } + } + if (chosen == null) { for (Direction d : host.getTargets()) { - if (serverLevel.getBlockEntity(pbe.getBlockPos().relative(d)) != null) { chosen = d; break; } + if (!serverLevel.getBlockState(pbe.getBlockPos().relative(d)).isAir()) { chosen = d; break; } } - if (chosen == null) { - for (Direction d : host.getTargets()) { - if (!serverLevel.getBlockState(pbe.getBlockPos().relative(d)).isAir()) { chosen = d; break; } - } - } - if (chosen != null) { - BlockPos targetPos = pbe.getBlockPos().relative(chosen); - var state2 = serverLevel.getBlockState(targetPos); - var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); - InteractionResult r = state2.use(serverLevel, player, hand, hit); - if (r.consumesAction()) { - LogUtils.getLogger().info("EAP[S]: opened via simulated use at {} ({}), result={}", targetPos, chosen, r); - context.setPacketHandled(true); - return; - } + } + if (chosen != null) { + BlockPos targetPos = pbe.getBlockPos().relative(chosen); + var state2 = serverLevel.getBlockState(targetPos); + var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false); + InteractionResult r = state2.use(serverLevel, player, hand, hit); + LogUtils.getLogger().info("EAP[S]: simulated use on {}, face={}, result={}", targetPos, chosen, r); + if (r.consumesAction()) { + context.setPacketHandled(true); + return; } } } diff --git a/src/main/java/com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java b/src/main/java/com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java new file mode 100644 index 0000000..216546c --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java @@ -0,0 +1,115 @@ +package com.extendedae_plus.network; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.networking.IGrid; +import appeng.api.networking.security.IActionHost; +import appeng.api.stacks.AEKey; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.me.service.CraftingService; +import appeng.menu.AEBaseMenu; +import appeng.menu.me.crafting.CraftingCPUMenu; +import appeng.menu.locator.MenuLocators; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; +import com.mojang.logging.LogUtils; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。 + * 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器, + * 打开该供应器自身的 UI(不是目标机器的 UI)。 + */ +public class CraftingMonitorOpenProviderC2SPacket { + private final AEKey what; + + public CraftingMonitorOpenProviderC2SPacket(AEKey what) { + this.what = what; + } + + public static void encode(CraftingMonitorOpenProviderC2SPacket msg, FriendlyByteBuf buf) { + AEKey.writeKey(buf, msg.what); + } + + public static CraftingMonitorOpenProviderC2SPacket decode(FriendlyByteBuf buf) { + AEKey key = AEKey.readKey(buf); + return new CraftingMonitorOpenProviderC2SPacket(key); + } + + public static void handle(CraftingMonitorOpenProviderC2SPacket msg, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player == null) return; + + LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorOpenProviderC2SPacket key={} from {}", msg.what, player.getGameProfile().getName()); + + // 必须在 CraftingCPU 界面内 + if (!(player.containerMenu instanceof CraftingCPUMenu menu)) { + LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort"); + return; + } + + // 通过菜单的 target(可能是 BlockEntity/Part/ItemHost),按 IActionHost 获取 Grid + IGrid grid = null; + Object target = ((AEBaseMenu) menu).getTarget(); + if (target instanceof IActionHost host && host.getActionableNode() != null) { + grid = host.getActionableNode().getGrid(); + } + if (grid == null) { + LogUtils.getLogger().info("EAP[S]: grid is null, abort"); + return; + } + + var cs = grid.getCraftingService(); + if (!(cs instanceof CraftingService craftingService)) { + LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort"); + return; + } + + // 1) 根据 AEKey 找到可能的样板(pattern) + Collection patterns = craftingService.getCraftingFor(msg.what); + LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what); + if (patterns.isEmpty()) { + return; + } + + // 2) 遍历提供该样板的 Provider,定位 PatternProviderLogic + for (var pattern : patterns) { + var providers = craftingService.getProviders(pattern); + for (var provider : providers) { + if (provider instanceof PatternProviderLogic ppl) { + // accessor 获取 host + PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); + if (host == null) continue; + var pbe = host.getBlockEntity(); + if (pbe == null) continue; + // 在服务端上下文中执行,pbe 仅用于构造菜单定位器 + + // 直接打开供应器自身的 UI(调用 Host 默认方法) + try { + // 部件与方块实体分别选择定位器 + if (host instanceof appeng.parts.AEBasePart part) { + host.openMenu(player, MenuLocators.forPart(part)); + } else { + host.openMenu(player, MenuLocators.forBlockEntity(pbe)); + } + context.setPacketHandled(true); + return; + } catch (Throwable t) { + LogUtils.getLogger().error("EAP[S]: open provider UI failed at {}", pbe.getBlockPos(), t); + } + } + } + } + + LogUtils.getLogger().info("EAP[S]: no provider UI opened for key={}", msg.what); + }); + context.setPacketHandled(true); + } +} diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/network/ModNetwork.java index cc50a47..3cfe50a 100644 --- a/src/main/java/com/extendedae_plus/network/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/network/ModNetwork.java @@ -77,6 +77,12 @@ public class ModNetwork { .decoder(CraftingMonitorJumpC2SPacket::decode) .consumerNetworkThread(CraftingMonitorJumpC2SPacket::handle) .add(); + + CHANNEL.messageBuilder(CraftingMonitorOpenProviderC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(CraftingMonitorOpenProviderC2SPacket::encode) + .decoder(CraftingMonitorOpenProviderC2SPacket::decode) + .consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index f66260c..e515bd3 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -31,7 +31,6 @@ "ae2.PatternEncodingTermMenuMixin", "ae2.PatternProviderLogicAdvancedMixin", "ae2.PatternProviderMenuAdvancedMixin", - "ae2.accessor.CraftingCPUMenuAccessor", "ae2.accessor.MEStorageMenuAccessor", "ae2.accessor.PatternEncodingTermMenuAccessor", "ae2.accessor.PatternProviderLogicAccessor",