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

This commit is contained in:
GaLi 2025-08-27 22:19:55 +08:00
parent 3540c52676
commit a75d7fbc5c
7 changed files with 186 additions and 44 deletions

View File

@ -21,7 +21,7 @@ import java.util.List;
import java.util.function.Function;
/**
* 参照 MAE2 DynamicCraftingCubeModelProvider实现形成态光照模型
* 形成态光照模型
*/
public class EPlusCraftingCubeModelProvider
extends AbstractCraftingUnitModelProvider<EPlusCraftingUnitType> {
@ -29,13 +29,13 @@ public class EPlusCraftingCubeModelProvider
public static final ChunkRenderTypeSet CUTOUT = ChunkRenderTypeSet.of(RenderType.cutout());
private static final List<Material> 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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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