增加合成监控界面shift左键打开机器ui

This commit is contained in:
GaLi 2025-08-27 12:36:18 +08:00
parent b2b6b24231
commit 3540c52676
5 changed files with 216 additions and 0 deletions

View File

@ -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<Boolean> 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();

View File

@ -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();
}

View File

@ -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<NetworkEvent.Context> 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<IPatternDetails> 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);
}
}

View File

@ -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++; }

View File

@ -31,6 +31,7 @@
"ae2.PatternEncodingTermMenuMixin",
"ae2.PatternProviderLogicAdvancedMixin",
"ae2.PatternProviderMenuAdvancedMixin",
"ae2.accessor.CraftingCPUMenuAccessor",
"ae2.accessor.MEStorageMenuAccessor",
"ae2.accessor.PatternEncodingTermMenuAccessor",
"ae2.accessor.PatternProviderLogicAccessor",