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] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E6=88=90=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E7=95=8C=E9=9D=A2shift=E5=B7=A6=E9=94=AE=E6=89=93?= =?UTF-8?q?=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",