diff --git a/build.gradle b/build.gradle index ace6636..54886d7 100644 --- a/build.gradle +++ b/build.gradle @@ -145,6 +145,7 @@ dependencies { compileOnly "curse.maven:jade-324717:5427817" compileOnly "me.shedaniel:RoughlyEnoughItems-neoforge:16.0.729" compileOnly "curse.maven:applied-mekanistics-574300:5978711" + compileOnly "mekanism:Mekanism:1.21.1-10.7.15.80:api" compileOnly "dev.latvian.mods:kubejs-neoforge:2101.7.1-build.188" compileOnly "dev.latvian.mods:rhino:2101.2.5-build.54" compileOnly "curse.maven:framedblocks-441647:6127891" diff --git a/readme.md b/readme.md index b3ada76..477b256 100644 --- a/readme.md +++ b/readme.md @@ -7,11 +7,5 @@ _这个分支是粗浅的EMI适配,技术力不足,请见谅_ ### 增加/更改的QoL功能 - 在EMI配方/配方树界面快速拉起样板编写&上传 - 扩展了样板供应器选择界面的搜索功能 - - 现在可以匹配样板供应器目标方块的本地化键名 - - 允许在多个获取到的关键词间切换 - - 默认顺序: - - | 别名 _(不包含特殊字符的字符串现在都会被视为别名)_ - - | EMI配方类别本地化关键词 - - | 原版配方关键词 - - ↓ EMI配方关键词 +- 更强大的远程打开机器UI功能 - 更多功能正在极速构思中 _(您知道吗? 这里的构思与构思谐音, 令人忍俊不禁)_ \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/MixinAbstractContainerMenu.java b/src/main/java/com/extendedae_plus/mixin/MixinAbstractContainerMenu.java new file mode 100644 index 0000000..e3735b9 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/MixinAbstractContainerMenu.java @@ -0,0 +1,20 @@ +package com.extendedae_plus.mixin; + +import net.minecraft.world.inventory.AbstractContainerMenu; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(AbstractContainerMenu.class) +public abstract class MixinAbstractContainerMenu { +// @Inject(method = "stillValid(Lnet/minecraft/world/inventory/ContainerLevelAccess;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/level/block/Block;)Z", +// at = @At("HEAD"), cancellable = true) +// private static void eaep$cancelCheck(ContainerLevelAccess access, Player player, Block targetBlock, CallbackInfoReturnable cir) { +// cir.setReturnValue(access.evaluate(((level, pos) -> { +// for (Direction direction : Direction.values()) { +// if (level.getBlockState(pos.relative(direction)).getBlock() +// instanceof PatternProviderBlock) +// return true; +// } +// return level.getBlockState(pos).is(targetBlock) && player.canInteractWithBlock(pos, 4.0F); +// }), true)); +// } +} diff --git a/src/main/java/com/extendedae_plus/mixin/MixinPlayer.java b/src/main/java/com/extendedae_plus/mixin/MixinPlayer.java new file mode 100644 index 0000000..9bb42b9 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/MixinPlayer.java @@ -0,0 +1,24 @@ +package com.extendedae_plus.mixin; + +import appeng.block.AEBaseEntityBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Player.class) +public class MixinPlayer { + @Inject(method = "canInteractWithBlock", at = @At("HEAD"), cancellable = true) + private void eaep$cancelCheck(BlockPos pos, double distance, CallbackInfoReturnable cir) { + var self = (Player)(Object) this; + var level = self.level(); + for (Direction direction : Direction.values()) { + if (level.getBlockState(pos.relative(direction)).getBlock() + instanceof AEBaseEntityBlock) + cir.setReturnValue(true); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java index b423345..046afc9 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java @@ -75,10 +75,6 @@ public abstract class AEBaseScreenMixin { if (key == null) { return; } - // Debug: 标记一次发送 - try { - LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key); - } catch (Throwable ignored2) {} PacketDistributor.sendToServer(new CraftingMonitorJumpC2SPacket(key)); cir.setReturnValue(true); } catch (Throwable ignored) { @@ -186,8 +182,8 @@ public abstract class AEBaseScreenMixin { var details = PatternDetailsHelper.decodePattern(itemStack, Minecraft.getInstance().level); try { if (details != null && details.getOutputs() != null && !details.getOutputs().isEmpty()) { - AEKey key = details.getOutputs().get(0).what(); - if (key != null && ClientPatternHighlightStore.hasHighlight(key)) { + AEKey key = details.getOutputs().getFirst().what(); + if (ClientPatternHighlightStore.hasHighlight(key)) { try { GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y); } catch (Throwable ignored) {} @@ -227,7 +223,7 @@ public abstract class AEBaseScreenMixin { // 只关心第一行(标题类文本无换行或 maxWidth<=0) var contentLine = (text.getMaxWidth() <= 0) ? content.getVisualOrderText() - : font.split(content, text.getMaxWidth()).get(0); + : font.split(content, text.getMaxWidth()).getFirst(); int lineWidth = font.width(contentLine); int x = pos.getX(); @@ -313,13 +309,13 @@ public abstract class AEBaseScreenMixin { @Shadow - protected void setTextContent(String id, Component content) {}; + protected void setTextContent(String id, Component content) {} @Inject(method = "updateBeforeRender", at = @At("RETURN"), remap = false) private void onUpdateBeforeRender(CallbackInfo ci) { try { AEBaseScreen self = (AEBaseScreen) (Object) this; - if (self instanceof PatternProviderScreen screen){ + if (self instanceof PatternProviderScreen screen){ Component t = screen.getTitle(); if (t != null && !t.getString().isEmpty()) { this.setTextContent(AEBaseScreen.TEXT_ID_DIALOG_TITLE, t); diff --git a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java index c5e6e19..4c54acd 100644 --- a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java @@ -1,126 +1,129 @@ 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.parts.IPart; import appeng.api.stacks.AEKey; +import appeng.blockentity.AEBaseBlockEntity; +import appeng.blockentity.crafting.PatternProviderBlockEntity; +import appeng.blockentity.misc.InterfaceBlockEntity; +import appeng.blockentity.networking.CableBusBlockEntity; 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.ExtendedAEPlus; import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; -import com.mojang.logging.LogUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 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.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.network.handling.IPayloadContext; -import java.util.Collection; +import java.util.ArrayList; +import java.util.List; /** * 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。 * 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器, * 尝试打开其目标机器的 GUI。 */ -public class CraftingMonitorJumpC2SPacket implements CustomPacketPayload { +public record CraftingMonitorJumpC2SPacket(AEKey what) implements CustomPacketPayload { public static final Type TYPE = new Type<>( - ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "crafting_monitor_jump")); - - public static final StreamCodec STREAM_CODEC = StreamCodec.of( - (buf, pkt) -> AEKey.writeKey(buf, pkt.what), - buf -> new CraftingMonitorJumpC2SPacket(AEKey.readKey(buf)) - ); - private final AEKey what; - - public CraftingMonitorJumpC2SPacket(AEKey what) { - this.what = what; - } + ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "crafting_monitor_jump")); @Override public Type type() { return TYPE; } - public static void handle(final CraftingMonitorJumpC2SPacket msg, final IPayloadContext ctx) { - ctx.enqueueWork(() -> { - if (!(ctx.player() instanceof ServerPlayer player)) return; - LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName()); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + AEKey.STREAM_CODEC, CraftingMonitorJumpC2SPacket::what, + CraftingMonitorJumpC2SPacket::new); - // 必须在 CraftingCPU 界面内 - if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) { - LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort"); - return; - } + public static void handle(final CraftingMonitorJumpC2SPacket packet, final IPayloadContext ctx) { + Player player = ctx.player(); + Level level = player.level(); + if (level.isClientSide) return; + ctx.enqueueWork(() -> openMenu(player, level, packet)); + } - // 通过菜单 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; - } + private static void openMenu(Player player, Level level, CraftingMonitorJumpC2SPacket packet) { + if (!(player.containerMenu instanceof CraftingCPUMenu menu)) return; - var cs = grid.getCraftingService(); - if (!(cs instanceof CraftingService craftingService)) { - LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort"); - return; - } + // 通过菜单 target(可能是 BlockEntity/Part/ItemHost)按 IActionHost 获取 Grid + IGrid grid = null; + Object target = (menu).getTarget(); + if (target instanceof IActionHost host && host.getActionableNode() != null) + grid = host.getActionableNode().getGrid(); + if (grid == null) 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; - } + var cs = grid.getCraftingService(); + if (!(cs instanceof CraftingService craftingService)) 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(); - ServerLevel serverLevel = player.serverLevel(); + // 根据 AEKey 找到可能的 Pattern, 遍历提供该样板的 Provider + List providers = new ArrayList<>(); + craftingService.getCraftingFor(packet.what).stream() + .map(craftingService::getProviders) + .forEach(iteration -> + iteration.forEach(providers::addLast)); - // 尝试对邻居打开 GUI(优先通过 MenuProvider) - 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); - player.openMenu(provider1, targetPos); - 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); - player.openMenu(provider2, targetPos); - return; - } - } + providers.forEach(provider -> { + if (!(provider instanceof PatternProviderLogic ppl)) return; - // 兜底:若无 MenuProvider,则跳过(不再模拟右键以确保兼容性) - } + // 使用 accessor 获取 host(受保护字段通过 accessor 访问) + PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); + if (host == null) return; + var pbe = host.getBlockEntity(); + + List delayedBlocks = new ArrayList<>(); + + // 尝试对邻居打开 GUI(优先通过 MenuProvider) + for (Direction dir : host.getTargets()) { + BlockPos targetPos = pbe.getBlockPos().relative(dir); + + var tbe = level.getBlockEntity(targetPos); + if (tbe instanceof MenuProvider provider1) { + player.openMenu(provider1, targetPos); + return; + } + + var tState = level.getBlockState(targetPos); + var provider2 = tState.getMenuProvider(level, targetPos); + if (provider2 != null) { + player.openMenu(provider2, targetPos); + return; + } + + // awc这AE怎么这么坏啊😭😭😭 + if (!(tbe instanceof AEBaseBlockEntity)) continue; + switch (tbe) { + case InterfaceBlockEntity ignored -> delayedBlocks.addFirst(dir); + case PatternProviderBlockEntity ignored -> delayedBlocks.addLast(dir); + case CableBusBlockEntity bus when bus.getCableBus().getPart(dir.getOpposite()) != null -> + delayedBlocks.addFirst(dir); + default -> tState.useWithoutItem(level, player, + new BlockHitResult(player.position(), dir.getOpposite(), targetPos, false)); } - LogUtils.getLogger().info("EAP[S]: providers count for one pattern: {}", providerCount); } - LogUtils.getLogger().info("EAP[S]: no target opened for key={}", msg.what); + if (delayedBlocks.isEmpty()) return; + BlockPos pos = pbe.getBlockPos().relative(delayedBlocks.getFirst()); + + if (level.getBlockEntity(pos) instanceof CableBusBlockEntity bus) { + IPart part = bus.getCableBus().getPart(delayedBlocks.getFirst().getOpposite()); + if (part != null) part.onUseWithoutItem(player, new Vec3(pos.getX(), pos.getY(), pos.getZ())); + } else { + level.getBlockState(pos).useWithoutItem(level, player, new BlockHitResult( + player.position(), delayedBlocks.getFirst().getOpposite(), pos, false)); + } }); } } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index a5496b9..bdeb9de 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -3,6 +3,8 @@ "package": "com.extendedae_plus.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ + "MixinAbstractContainerMenu", + "MixinPlayer", "ae2.AEProcessingPatternMixin", "ae2.CraftingCPUClusterMixin", "ae2.EncodedPatternItemMixin",