扩展了远程打开机器UI的功能, AE啊AE你怎么这么独特<(≧□≦)>
This commit is contained in:
parent
7eb9669180
commit
0ff7ba80dc
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -7,11 +7,5 @@ _这个分支是粗浅的EMI适配,技术力不足,请见谅_
|
|||
### 增加/更改的QoL功能
|
||||
- 在EMI配方/配方树界面快速拉起样板编写&上传
|
||||
- 扩展了样板供应器选择界面的搜索功能
|
||||
- 现在可以匹配样板供应器目标方块的本地化键名
|
||||
- 允许在多个获取到的关键词间切换
|
||||
- 默认顺序:
|
||||
- | 别名 _(不包含特殊字符的字符串现在都会被视为别名)_
|
||||
- | EMI配方类别本地化关键词
|
||||
- | 原版配方关键词
|
||||
- ↓ EMI配方关键词
|
||||
- 更强大的远程打开机器UI功能
|
||||
- 更多功能正在极速构思中 _(您知道吗? 这里的构思与构思谐音, 令人忍俊不禁)_
|
||||
|
|
@ -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));
|
||||
// }
|
||||
}
|
||||
24
src/main/java/com/extendedae_plus/mixin/MixinPlayer.java
Normal file
24
src/main/java/com/extendedae_plus/mixin/MixinPlayer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<CraftingMonitorJumpC2SPacket> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.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;
|
||||
}
|
||||
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "crafting_monitor_jump"));
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> 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<RegistryFriendlyByteBuf, CraftingMonitorJumpC2SPacket> 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<IPatternDetails> 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<ICraftingProvider> 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<Direction> 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
"package": "com.extendedae_plus.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"MixinAbstractContainerMenu",
|
||||
"MixinPlayer",
|
||||
"ae2.AEProcessingPatternMixin",
|
||||
"ae2.CraftingCPUClusterMixin",
|
||||
"ae2.EncodedPatternItemMixin",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user