扩展了远程打开机器UI的功能, AE啊AE你怎么这么独特<(≧□≦)>

This commit is contained in:
XianYuFish001 2025-09-26 20:14:16 +08:00
parent 7eb9669180
commit 0ff7ba80dc
7 changed files with 139 additions and 99 deletions

View File

@ -145,6 +145,7 @@ dependencies {
compileOnly "curse.maven:jade-324717:5427817" compileOnly "curse.maven:jade-324717:5427817"
compileOnly "me.shedaniel:RoughlyEnoughItems-neoforge:16.0.729" compileOnly "me.shedaniel:RoughlyEnoughItems-neoforge:16.0.729"
compileOnly "curse.maven:applied-mekanistics-574300:5978711" 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:kubejs-neoforge:2101.7.1-build.188"
compileOnly "dev.latvian.mods:rhino:2101.2.5-build.54" compileOnly "dev.latvian.mods:rhino:2101.2.5-build.54"
compileOnly "curse.maven:framedblocks-441647:6127891" compileOnly "curse.maven:framedblocks-441647:6127891"

View File

@ -7,11 +7,5 @@ _这个分支是粗浅的EMI适配,技术力不足,请见谅_
### 增加/更改的QoL功能 ### 增加/更改的QoL功能
- 在EMI配方/配方树界面快速拉起样板编写&上传 - 在EMI配方/配方树界面快速拉起样板编写&上传
- 扩展了样板供应器选择界面的搜索功能 - 扩展了样板供应器选择界面的搜索功能
- 现在可以匹配样板供应器目标方块的本地化键名 - 更强大的远程打开机器UI功能
- 允许在多个获取到的关键词间切换
- 默认顺序:
- | 别名 _(不包含特殊字符的字符串现在都会被视为别名)_
- | EMI配方类别本地化关键词
- | 原版配方关键词
- ↓ EMI配方关键词
- 更多功能正在极速构思中 _(您知道吗? 这里的构思与构思谐音, 令人忍俊不禁)_ - 更多功能正在极速构思中 _(您知道吗? 这里的构思与构思谐音, 令人忍俊不禁)_

View File

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

View File

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

View File

@ -75,10 +75,6 @@ public abstract class AEBaseScreenMixin {
if (key == null) { if (key == null) {
return; return;
} }
// Debug: 标记一次发送
try {
LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key);
} catch (Throwable ignored2) {}
PacketDistributor.sendToServer(new CraftingMonitorJumpC2SPacket(key)); PacketDistributor.sendToServer(new CraftingMonitorJumpC2SPacket(key));
cir.setReturnValue(true); cir.setReturnValue(true);
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -186,8 +182,8 @@ public abstract class AEBaseScreenMixin {
var details = PatternDetailsHelper.decodePattern(itemStack, Minecraft.getInstance().level); var details = PatternDetailsHelper.decodePattern(itemStack, Minecraft.getInstance().level);
try { try {
if (details != null && details.getOutputs() != null && !details.getOutputs().isEmpty()) { if (details != null && details.getOutputs() != null && !details.getOutputs().isEmpty()) {
AEKey key = details.getOutputs().get(0).what(); AEKey key = details.getOutputs().getFirst().what();
if (key != null && ClientPatternHighlightStore.hasHighlight(key)) { if (ClientPatternHighlightStore.hasHighlight(key)) {
try { try {
GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y); GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y);
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
@ -227,7 +223,7 @@ public abstract class AEBaseScreenMixin {
// 只关心第一行标题类文本无换行或 maxWidth<=0 // 只关心第一行标题类文本无换行或 maxWidth<=0
var contentLine = (text.getMaxWidth() <= 0) var contentLine = (text.getMaxWidth() <= 0)
? content.getVisualOrderText() ? content.getVisualOrderText()
: font.split(content, text.getMaxWidth()).get(0); : font.split(content, text.getMaxWidth()).getFirst();
int lineWidth = font.width(contentLine); int lineWidth = font.width(contentLine);
int x = pos.getX(); int x = pos.getX();
@ -313,13 +309,13 @@ public abstract class AEBaseScreenMixin {
@Shadow @Shadow
protected void setTextContent(String id, Component content) {}; protected void setTextContent(String id, Component content) {}
@Inject(method = "updateBeforeRender", at = @At("RETURN"), remap = false) @Inject(method = "updateBeforeRender", at = @At("RETURN"), remap = false)
private void onUpdateBeforeRender(CallbackInfo ci) { private void onUpdateBeforeRender(CallbackInfo ci) {
try { try {
AEBaseScreen<?> self = (AEBaseScreen<?>) (Object) this; AEBaseScreen<?> self = (AEBaseScreen<?>) (Object) this;
if (self instanceof PatternProviderScreen screen){ if (self instanceof PatternProviderScreen<?> screen){
Component t = screen.getTitle(); Component t = screen.getTitle();
if (t != null && !t.getString().isEmpty()) { if (t != null && !t.getString().isEmpty()) {
this.setTextContent(AEBaseScreen.TEXT_ID_DIALOG_TITLE, t); this.setTextContent(AEBaseScreen.TEXT_ID_DIALOG_TITLE, t);

View File

@ -1,126 +1,129 @@
package com.extendedae_plus.network; package com.extendedae_plus.network;
import appeng.api.crafting.IPatternDetails;
import appeng.api.networking.IGrid; import appeng.api.networking.IGrid;
import appeng.api.networking.crafting.ICraftingProvider;
import appeng.api.networking.security.IActionHost; import appeng.api.networking.security.IActionHost;
import appeng.api.parts.IPart;
import appeng.api.stacks.AEKey; 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.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.me.service.CraftingService; 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.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
import com.mojang.logging.LogUtils;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation; 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.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 net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.Collection; import java.util.ArrayList;
import java.util.List;
/** /**
* 客户端从 CraftingCPUScreen 发送鼠标下条目对应的 AEKey * 客户端从 CraftingCPUScreen 发送鼠标下条目对应的 AEKey
* 服务端在当前打开的 CraftingCPUMenu 所属网络中定位匹配该 AEKey 的样板供应器 * 服务端在当前打开的 CraftingCPUMenu 所属网络中定位匹配该 AEKey 的样板供应器
* 尝试打开其目标机器的 GUI * 尝试打开其目标机器的 GUI
*/ */
public class CraftingMonitorJumpC2SPacket implements CustomPacketPayload { public record CraftingMonitorJumpC2SPacket(AEKey what) implements CustomPacketPayload {
public static final Type<CraftingMonitorJumpC2SPacket> TYPE = new Type<>( public static final Type<CraftingMonitorJumpC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "crafting_monitor_jump")); ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "crafting_monitor_jump"));
public static final StreamCodec<RegistryFriendlyByteBuf, CraftingMonitorJumpC2SPacket> 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;
}
@Override @Override
public Type<? extends CustomPacketPayload> type() { public Type<? extends CustomPacketPayload> type() {
return TYPE; return TYPE;
} }
public static void handle(final CraftingMonitorJumpC2SPacket msg, final IPayloadContext ctx) { public static final StreamCodec<RegistryFriendlyByteBuf, CraftingMonitorJumpC2SPacket> STREAM_CODEC = StreamCodec.composite(
ctx.enqueueWork(() -> { AEKey.STREAM_CODEC, CraftingMonitorJumpC2SPacket::what,
if (!(ctx.player() instanceof ServerPlayer player)) return; CraftingMonitorJumpC2SPacket::new);
LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName());
// 必须在 CraftingCPU 界面内 public static void handle(final CraftingMonitorJumpC2SPacket packet, final IPayloadContext ctx) {
if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) { Player player = ctx.player();
LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort"); Level level = player.level();
return; if (level.isClientSide) return;
ctx.enqueueWork(() -> openMenu(player, level, packet));
} }
private static void openMenu(Player player, Level level, CraftingMonitorJumpC2SPacket packet) {
if (!(player.containerMenu instanceof CraftingCPUMenu menu)) return;
// 通过菜单 target可能是 BlockEntity/Part/ItemHost IActionHost 获取 Grid // 通过菜单 target可能是 BlockEntity/Part/ItemHost IActionHost 获取 Grid
IGrid grid = null; IGrid grid = null;
Object target = ((appeng.menu.AEBaseMenu) menu).getTarget(); Object target = (menu).getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) { if (target instanceof IActionHost host && host.getActionableNode() != null)
grid = host.getActionableNode().getGrid(); grid = host.getActionableNode().getGrid();
} if (grid == null) return;
if (grid == null) {
LogUtils.getLogger().info("EAP[S]: grid is null, abort");
return;
}
var cs = grid.getCraftingService(); var cs = grid.getCraftingService();
if (!(cs instanceof CraftingService craftingService)) { if (!(cs instanceof CraftingService craftingService)) return;
LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort");
return;
}
// 1) 根据 AEKey 找到可能的样板pattern // 根据 AEKey 找到可能的 Pattern, 遍历提供该样板的 Provider
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what); List<ICraftingProvider> providers = new ArrayList<>();
LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what); craftingService.getCraftingFor(packet.what).stream()
if (patterns.isEmpty()) { .map(craftingService::getProviders)
return; .forEach(iteration ->
} iteration.forEach(providers::addLast));
providers.forEach(provider -> {
if (!(provider instanceof PatternProviderLogic ppl)) 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 访问 // 使用 accessor 获取 host受保护字段通过 accessor 访问
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
if (host == null) continue; if (host == null) return;
var pbe = host.getBlockEntity(); var pbe = host.getBlockEntity();
ServerLevel serverLevel = player.serverLevel();
List<Direction> delayedBlocks = new ArrayList<>();
// 尝试对邻居打开 GUI优先通过 MenuProvider // 尝试对邻居打开 GUI优先通过 MenuProvider
for (Direction dir : host.getTargets()) { for (Direction dir : host.getTargets()) {
BlockPos targetPos = pbe.getBlockPos().relative(dir); BlockPos targetPos = pbe.getBlockPos().relative(dir);
var tbe = serverLevel.getBlockEntity(targetPos);
var tbe = level.getBlockEntity(targetPos);
if (tbe instanceof MenuProvider provider1) { if (tbe instanceof MenuProvider provider1) {
LogUtils.getLogger().info("EAP[S]: open screen via MenuProvider at {}", targetPos);
player.openMenu(provider1, targetPos); player.openMenu(provider1, targetPos);
return; return;
} }
var tstate = serverLevel.getBlockState(targetPos);
var provider2 = tstate.getMenuProvider(serverLevel, targetPos); var tState = level.getBlockState(targetPos);
var provider2 = tState.getMenuProvider(level, targetPos);
if (provider2 != null) { if (provider2 != null) {
LogUtils.getLogger().info("EAP[S]: open screen via state.getMenuProvider at {}", targetPos);
player.openMenu(provider2, targetPos); player.openMenu(provider2, targetPos);
return; return;
} }
}
// 兜底若无 MenuProvider则跳过不再模拟右键以确保兼容性 // 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); 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));
} }
LogUtils.getLogger().info("EAP[S]: no target opened for key={}", msg.what);
}); });
} }
} }

View File

@ -3,6 +3,8 @@
"package": "com.extendedae_plus.mixin", "package": "com.extendedae_plus.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [ "mixins": [
"MixinAbstractContainerMenu",
"MixinPlayer",
"ae2.AEProcessingPatternMixin", "ae2.AEProcessingPatternMixin",
"ae2.CraftingCPUClusterMixin", "ae2.CraftingCPUClusterMixin",
"ae2.EncodedPatternItemMixin", "ae2.EncodedPatternItemMixin",