网络包

This commit is contained in:
GaLicn 2025-09-06 14:07:39 +08:00
parent 5fe2deb0df
commit a18614ca7b
15 changed files with 354 additions and 456 deletions

View File

@ -127,96 +127,68 @@ sourceSets.main.resources { srcDir 'src/generated/resources' }
//
sourceSets.main.java {
// ExtendedAE GuiExPatternProviderActionEPPButton
// ExtendedAE GuiExPatternProviderActionEPPButton
// CurseMaven Jar
srcDir 'othermods/ExtendedAE-1.21-2.2.21-neoforge/src/main/java'
// 广ExtendedAEPlusExtendedAEPlusClientConfig
// api/** include
include 'com/extendedae_plus/api/**'
exclude 'com/extendedae_plus/client/**'
//
include 'com/extendedae_plus/config/**'
exclude 'com/extendedae_plus/hooks/**'
exclude 'com/extendedae_plus/init/**'
exclude 'com/extendedae_plus/integration/**'
exclude 'com/extendedae_plus/menu/**'
// Mod
// GUI/ Forge API
include 'com/extendedae_plus/ExtendedAEPlus.java'
// mixin accessor
// accessor
include 'com/extendedae_plus/mixin/ae2/accessor/**'
// PatternProviderLogic /
include 'com/extendedae_plus/mixin/ae2/helpers/**'
// AEProcessingPattern mixin
include 'com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java'
// CraftingTreeProcess + accessor
include 'com/extendedae_plus/mixin/ae2/autopattern/**'
// GUI A GUI
include 'com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java'
include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java'
include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java'
// ExtendedAE GUI NewIcon
// Provider GUI 使广 exclude include
include 'com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java'
include 'com/extendedae_plus/NewIcon.java'
// network/** include
// util
include 'com/extendedae_plus/ExtendedAEPlusClient.java'
include 'com/extendedae_plus/config/**'
include 'com/extendedae_plus/api/**'
// util
include 'com/extendedae_plus/util/ExtendedAELogger.java'
include 'com/extendedae_plus/util/PatternProviderDataUtil.java'
include 'com/extendedae_plus/util/PatternProviderUIHelper.java'
//
include 'com/extendedae_plus/util/PatternScaler.java'
include 'com/extendedae_plus/util/RequestedAmountHolder.java'
// GUI
// api
include 'com/extendedae_plus/api/**'
include 'com/extendedae_plus/util/ExtendedAELogger.java'
// content
// util needed by network payloads (providers listing / upload helpers)
include 'com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java'
// content
include 'com/extendedae_plus/content/ClientPatternHighlightStore.java'
//
include 'com/extendedae_plus/content/ScaledProcessingPattern.java'
// client-side helpers needed by S2C payloads
include 'com/extendedae_plus/client/ClientAdvancedBlockingState.java'
include 'com/extendedae_plus/client/ui/ProviderSelectScreen.java'
// NeoForge 1.21 Payload API版本
include 'com/extendedae_plus/network/ModNetwork.java'
include 'com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java'
include 'com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java'
include 'com/extendedae_plus/network/ScalePatternsC2SPacket.java'
include 'com/extendedae_plus/network/SetPatternHighlightS2CPacket.java'
// include util util/**
include 'com/extendedae_plus/util/PatternProviderDataUtil.java'
include 'com/extendedae_plus/util/PatternProviderUIHelper.java'
include 'com/extendedae_plus/util/ExtendedAELogger.java'
exclude 'com/extendedae_plus/wireless/**'
include 'com/extendedae_plus/network/AdvancedBlockingSyncS2CPacket.java'
include 'com/extendedae_plus/network/ProvidersListS2CPacket.java'
include 'com/extendedae_plus/network/RequestProvidersListC2SPacket.java'
include 'com/extendedae_plus/network/SetProviderPageS2CPacket.java'
include 'com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java'
include 'com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java'
include 'com/extendedae_plus/network/OpenProviderUiC2SPacket.java'
include 'com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java'
include 'com/extendedae_plus/network/UploadEncodedPatternToProviderC2SPacket.java'
// AE2 mixinaccessor/helpers/autopattern/gui/menu
include 'com/extendedae_plus/mixin/ae2/accessor/**'
include 'com/extendedae_plus/mixin/ae2/helpers/**'
include 'com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java'
include 'com/extendedae_plus/mixin/ae2/autopattern/**'
include 'com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java'
include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java'
include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java'
// ExtendedAE GUI NewIcon
include 'com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java'
include 'com/extendedae_plus/NewIcon.java'
}
// Sets up a dependency configuration called 'localRuntime'.
// This configuration should be used instead of 'runtimeOnly' to declare
// a dependency that will be present for runtime testing but that is
// "optional", meaning it will not be pulled by dependents of this mod.
configurations {
runtimeClasspath.extendsFrom localRuntime
}
dependencies {
// Example optional mod dependency with JEI
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
// compileOnly "mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}"
// We add the full version to localRuntime, not runtimeOnly, so that we do not publish a dependency on it
// localRuntime "mezz.jei:jei-${mc_version}-neoforge:${jei_version}"
// Example mod dependency using a mod jar from ./libs with a flat dir repository
// This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
// The group id is ignored when searching -- in this case, it is "blank"
// implementation "blank:coolmod-${mc_version}:${coolmod_version}"
// Example mod dependency using a file as dependency
// implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar")
// Example project dependency using a sister or child project:
// implementation project(":myproject")
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
// --- Added dependencies for target mods ---
implementation "curse.maven:glodium-957920:5821676"
implementation "org.appliedenergistics:appliedenergistics2:19.2.8"

View File

@ -44,4 +44,4 @@ mod_description=Example mod description.\nNewline characters can be used and wil
## UI item explorer selection (emi | rei | jei)
# Default to 'emi' per request; you can override by running with -Puse_Xei=rei or -Puse_Xei=jei
use_Xei=emi
use_Xei=jei

View File

@ -194,7 +194,10 @@ public class ProviderSelectScreen extends Screen {
private void onChoose(int idx) {
if (idx < 0 || idx >= fIds.size()) return;
long providerId = fIds.get(idx);
ModNetwork.CHANNEL.sendToServer(new UploadEncodedPatternToProviderC2SPacket(providerId));
var conn = Minecraft.getInstance().getConnection();
if (conn != null) {
conn.send(new UploadEncodedPatternToProviderC2SPacket(providerId));
}
this.onClose();
}
@ -342,12 +345,6 @@ public class ProviderSelectScreen extends Screen {
@Override
public void tick() {
super.tick();
if (searchBox != null) {
searchBox.tick();
}
if (cnInput != null) {
cnInput.tick();
}
if (needsRefresh) {
needsRefresh = false;
// 重新构建当前屏幕内容

View File

@ -54,3 +54,4 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
private void eap$debug_getShowInAccessTerminal(CallbackInfoReturnable<?> cir) {
}
}

View File

@ -1,12 +1,29 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.client.ClientAdvancedBlockingState;
import net.minecraft.network.FriendlyByteBuf;
import net.neoforged.neoforge.network.NetworkEvent;
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.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.function.Supplier;
/**
* S2C同步某个 Provider 的高级阻挡状态到客户端本地存储
*/
public class AdvancedBlockingSyncS2CPacket implements CustomPacketPayload {
public static final Type<AdvancedBlockingSyncS2CPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "adv_blocking_sync"));
public static final StreamCodec<RegistryFriendlyByteBuf, AdvancedBlockingSyncS2CPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeUtf(pkt.dimensionId);
buf.writeLong(pkt.blockPosLong);
buf.writeBoolean(pkt.enabled);
},
buf -> new AdvancedBlockingSyncS2CPacket(buf.readUtf(), buf.readLong(), buf.readBoolean())
);
public class AdvancedBlockingSyncS2CPacket {
private final String dimensionId;
private final long blockPosLong;
private final boolean enabled;
@ -17,25 +34,15 @@ public class AdvancedBlockingSyncS2CPacket {
this.enabled = enabled;
}
public static void encode(AdvancedBlockingSyncS2CPacket msg, FriendlyByteBuf buf) {
buf.writeUtf(msg.dimensionId);
buf.writeLong(msg.blockPosLong);
buf.writeBoolean(msg.enabled);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static AdvancedBlockingSyncS2CPacket decode(FriendlyByteBuf buf) {
String dim = buf.readUtf();
long pos = buf.readLong();
boolean en = buf.readBoolean();
return new AdvancedBlockingSyncS2CPacket(dim, pos, en);
}
public static void handle(AdvancedBlockingSyncS2CPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
public static void handle(final AdvancedBlockingSyncS2CPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
String key = ClientAdvancedBlockingState.key(msg.dimensionId, msg.blockPosLong);
ClientAdvancedBlockingState.set(key, msg.enabled);
});
ctx.setPacketHandled(true);
}
}

View File

@ -12,7 +12,10 @@ 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.FriendlyByteBuf;
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.InteractionHand;
@ -21,39 +24,37 @@ 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 net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.Collection;
import java.util.function.Supplier;
/**
* 客户端从 CraftingCPUScreen 发送鼠标下条目对应的 AEKey
* 服务端在当前打开的 CraftingCPUMenu 所属网络中定位匹配该 AEKey 的样板供应器
* 尝试打开其目标机器的 GUI
*/
public class CraftingMonitorJumpC2SPacket {
public class CraftingMonitorJumpC2SPacket 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;
}
public static void encode(CraftingMonitorJumpC2SPacket msg, FriendlyByteBuf buf) {
AEKey.writeKey(buf, msg.what);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
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;
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());
// 必须在 CraftingCPU 界面内
@ -102,54 +103,30 @@ public class CraftingMonitorJumpC2SPacket {
var pbe = host.getBlockEntity();
ServerLevel serverLevel = player.serverLevel();
// 尝试对邻居打开 GUI复用 OpenProviderUiC2SPacket 的策略
// 尝试对邻居打开 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);
NetworkHooks.openScreen(player, provider1, targetPos);
context.setPacketHandled(true);
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);
NetworkHooks.openScreen(player, provider2, targetPos);
context.setPacketHandled(true);
player.openMenu(provider2, targetPos);
return;
}
}
// 兜底若无 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.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);
LogUtils.getLogger().info("EAP[S]: simulated use on {}, face={}, result={}", targetPos, chosen, r);
if (r.consumesAction()) {
context.setPacketHandled(true);
return;
}
}
// 兜底若无 MenuProvider则跳过不再模拟右键以确保兼容性
}
}
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

@ -18,18 +18,20 @@ import com.glodblock.github.glodium.util.GlodUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
import static com.glodblock.github.extendedae.client.render.EAEHighlightHandler.highlight;
@ -38,27 +40,28 @@ import static com.glodblock.github.extendedae.client.render.EAEHighlightHandler.
* 服务端在当前打开的 CraftingCPUMenu 所属网络中定位匹配该 AEKey 的样板供应器
* 打开该供应器自身的 UI不是目标机器的 UI
*/
public class CraftingMonitorOpenProviderC2SPacket {
public class CraftingMonitorOpenProviderC2SPacket implements CustomPacketPayload {
public static final Type<CraftingMonitorOpenProviderC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "crafting_monitor_open_provider"));
public static final StreamCodec<RegistryFriendlyByteBuf, CraftingMonitorOpenProviderC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> AEKey.writeKey(buf, pkt.what),
buf -> new CraftingMonitorOpenProviderC2SPacket(AEKey.readKey(buf))
);
private final AEKey what;
public CraftingMonitorOpenProviderC2SPacket(AEKey what) {
this.what = what;
}
public static void encode(CraftingMonitorOpenProviderC2SPacket msg, FriendlyByteBuf buf) {
AEKey.writeKey(buf, msg.what);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
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;
public static void handle(final CraftingMonitorOpenProviderC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
// 必须在 CraftingCPU 界面内
if (!(player.containerMenu instanceof CraftingCPUMenu menu)) {
@ -119,15 +122,16 @@ public class CraftingMonitorOpenProviderC2SPacket {
if (foundSlot >= 0) {
int pageId = foundSlot / 36;
if (pageId > 0) {
// 发送 S2C 包通知客户端切换到指定页客户端会写入 mixin 字段并重排槽位
ModNetwork.CHANNEL.sendTo(new SetProviderPageS2CPacket(pageId), player.connection.connection, NetworkDirection.PLAY_TO_CLIENT);
// 发送 S2C切换到指定页
player.connection.send(new SetProviderPageS2CPacket(pageId));
}
}
// 最后发送高亮包保证界面已打开
if (pattern.getOutputs() != null && pattern.getOutputs().length > 0 && pattern.getOutputs()[0] != null) {
AEKey key = pattern.getOutputs()[0].what();
ModNetwork.CHANNEL.sendTo(new SetPatternHighlightS2CPacket(key, true), player.connection.connection, NetworkDirection.PLAY_TO_CLIENT);
var outs = pattern.getOutputs();
if (outs != null && !outs.isEmpty() && outs.get(0) != null) {
AEKey key = outs.get(0).what();
player.connection.send(new SetPatternHighlightS2CPacket(key, true));
}
return;
@ -137,7 +141,6 @@ public class CraftingMonitorOpenProviderC2SPacket {
}
}
});
context.setPacketHandled(true);
}
private static void highlightWithMessage(BlockPos pos, Direction face, ResourceKey<Level> dim, double multiplier, Player player) {

View File

@ -7,18 +7,21 @@ import appeng.blockentity.crafting.PatternProviderBlockEntity;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.parts.crafting.PatternProviderPart;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.api.AdvancedBlockingHolder;
import com.extendedae_plus.api.SmartDoublingHolder;
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
import appeng.api.networking.IInWorldGridNodeHost;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.Set;
import java.util.HashSet;
import java.util.function.Supplier;
/**
* C2S全网批量切换样板供应器的三种模式
@ -28,7 +31,19 @@ import java.util.function.Supplier;
*
* 负载为三个操作码各1字节分别对应blockingadvancedBlockingsmartDoubling
*/
public class GlobalToggleProviderModesC2SPacket {
public class GlobalToggleProviderModesC2SPacket implements CustomPacketPayload {
public static final Type<GlobalToggleProviderModesC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "global_toggle_provider_modes"));
public static final StreamCodec<FriendlyByteBuf, GlobalToggleProviderModesC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeByte(pkt.opBlocking.id);
buf.writeByte(pkt.opAdvancedBlocking.id);
buf.writeByte(pkt.opSmartDoubling.id);
buf.writeBlockPos(pkt.controllerPos);
},
buf -> new GlobalToggleProviderModesC2SPacket(Op.byId(buf.readByte()), Op.byId(buf.readByte()), Op.byId(buf.readByte()), buf.readBlockPos())
);
public enum Op {
NOOP((byte) 0),
SET_TRUE((byte) 1),
@ -58,32 +73,21 @@ public class GlobalToggleProviderModesC2SPacket {
this.controllerPos = controllerPos;
}
public static void encode(GlobalToggleProviderModesC2SPacket msg, FriendlyByteBuf buf) {
buf.writeByte(msg.opBlocking.id);
buf.writeByte(msg.opAdvancedBlocking.id);
buf.writeByte(msg.opSmartDoubling.id);
buf.writeBlockPos(msg.controllerPos);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static GlobalToggleProviderModesC2SPacket decode(FriendlyByteBuf buf) {
Op b = Op.byId(buf.readByte());
Op ab = Op.byId(buf.readByte());
Op sd = Op.byId(buf.readByte());
BlockPos pos = buf.readBlockPos();
return new GlobalToggleProviderModesC2SPacket(b, ab, sd, pos);
}
public static void handle(GlobalToggleProviderModesC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
public static void handle(final GlobalToggleProviderModesC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
if (!(ctx.player() instanceof ServerPlayer player)) return;
if (player == null) return;
// 从控制方块实体的 AE2 节点确定 AE 网络上下文
var level = player.serverLevel();
var be = level.getBlockEntity(msg.controllerPos);
if (!(be instanceof NetworkPatternControllerBlockEntity controller)) return;
var node = controller.getGridNode(null);
if (!(be instanceof IInWorldGridNodeHost host)) return;
var node = host.getGridNode(null);
if (node == null) return;
IGrid grid = node.getGrid();
if (grid == null) return;
@ -92,7 +96,6 @@ public class GlobalToggleProviderModesC2SPacket {
// 向发起玩家反馈影响数量便于判断按钮是否生效
player.displayClientMessage(Component.literal("E+ 全局切换已应用到 " + affected + " 个样板供应器"), true);
});
ctx.setPacketHandled(true);
}
private static int applyToAllProviders(IGrid grid, GlobalToggleProviderModesC2SPacket msg) {

View File

@ -11,5 +11,14 @@ public class ModNetwork {
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);
registrar.playToClient(SetPatternHighlightS2CPacket.TYPE, SetPatternHighlightS2CPacket.STREAM_CODEC, SetPatternHighlightS2CPacket::handle);
registrar.playToClient(AdvancedBlockingSyncS2CPacket.TYPE, AdvancedBlockingSyncS2CPacket.STREAM_CODEC, AdvancedBlockingSyncS2CPacket::handle);
registrar.playToClient(ProvidersListS2CPacket.TYPE, ProvidersListS2CPacket.STREAM_CODEC, ProvidersListS2CPacket::handle);
registrar.playToServer(RequestProvidersListC2SPacket.TYPE, RequestProvidersListC2SPacket.STREAM_CODEC, RequestProvidersListC2SPacket::handle);
registrar.playToClient(SetProviderPageS2CPacket.TYPE, SetProviderPageS2CPacket.STREAM_CODEC, SetProviderPageS2CPacket::handle);
registrar.playToServer(GlobalToggleProviderModesC2SPacket.TYPE, GlobalToggleProviderModesC2SPacket.STREAM_CODEC, GlobalToggleProviderModesC2SPacket::handle);
registrar.playToServer(CraftingMonitorJumpC2SPacket.TYPE, CraftingMonitorJumpC2SPacket.STREAM_CODEC, CraftingMonitorJumpC2SPacket::handle);
registrar.playToServer(CraftingMonitorOpenProviderC2SPacket.TYPE, CraftingMonitorOpenProviderC2SPacket.STREAM_CODEC, CraftingMonitorOpenProviderC2SPacket::handle);
registrar.playToServer(OpenProviderUiC2SPacket.TYPE, OpenProviderUiC2SPacket.STREAM_CODEC, OpenProviderUiC2SPacket::handle);
registrar.playToServer(UploadEncodedPatternToProviderC2SPacket.TYPE, UploadEncodedPatternToProviderC2SPacket.STREAM_CODEC, UploadEncodedPatternToProviderC2SPacket::handle);
}
}

View File

@ -3,6 +3,8 @@ package com.extendedae_plus.network;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.phys.BlockHitResult;
@ -15,12 +17,20 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.Level;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.NetworkHooks;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.function.Supplier;
public class OpenProviderUiC2SPacket implements CustomPacketPayload {
public static final Type<OpenProviderUiC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "open_provider_ui"));
public class OpenProviderUiC2SPacket {
public static final StreamCodec<FriendlyByteBuf, OpenProviderUiC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeLong(pkt.posLong);
buf.writeResourceLocation(pkt.dimId);
buf.writeVarInt(pkt.faceOrd);
},
buf -> new OpenProviderUiC2SPacket(buf.readLong(), buf.readResourceLocation(), buf.readVarInt())
);
private final long posLong;
private final ResourceLocation dimId;
private final int faceOrd; // 目前保留若目标需要可用
@ -31,26 +41,14 @@ public class OpenProviderUiC2SPacket {
this.faceOrd = faceOrd;
}
public static void encode(OpenProviderUiC2SPacket msg, FriendlyByteBuf buf) {
buf.writeLong(msg.posLong);
buf.writeResourceLocation(msg.dimId);
buf.writeVarInt(msg.faceOrd);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static OpenProviderUiC2SPacket decode(FriendlyByteBuf buf) {
long posLong = buf.readLong();
ResourceLocation dimId = buf.readResourceLocation();
int faceOrd = buf.readVarInt();
return new OpenProviderUiC2SPacket(posLong, dimId, faceOrd);
}
public static void handle(OpenProviderUiC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get();
context.enqueueWork(() -> {
ServerPlayer player = context.getSender();
if (player == null) return;
public static void handle(final OpenProviderUiC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
// 校验维度与方块
ResourceKey<Level> levelKey = ResourceKey.create(Registries.DIMENSION, msg.dimId);
@ -76,58 +74,19 @@ public class OpenProviderUiC2SPacket {
BlockPos targetPos = pos.relative(dir);
BlockEntity tbe = level.getBlockEntity(targetPos);
if (tbe instanceof MenuProvider provider) {
NetworkHooks.openScreen(player, provider, targetPos);
player.openMenu(provider, targetPos);
return;
}
var tstate = level.getBlockState(targetPos);
MenuProvider provider2 = tstate.getMenuProvider(level, targetPos);
if (provider2 != null) {
NetworkHooks.openScreen(player, provider2, targetPos);
player.openMenu(provider2, targetPos);
return;
}
}
// 如果邻居也未提供 MenuProvider则兜底尽量模拟一次徒手右键相邻方块
boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty();
if (anyHandEmpty) {
InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
if (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length) {
Direction dir = Direction.values()[msg.faceOrd];
BlockPos targetPos = pos.relative(dir);
var state2 = level.getBlockState(targetPos);
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false);
InteractionResult r = state2.use(level, player, hand, hit);
if (r.consumesAction()) {
return;
}
} else {
// 无明确朝向优先挑选有方块实体的邻居否则挑选非空气方块
Direction chosen = null;
for (Direction d : Direction.values()) {
if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; }
}
if (chosen == null) {
for (Direction d : Direction.values()) {
if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; }
}
}
if (chosen != null) {
BlockPos targetPos = pos.relative(chosen);
var state2 = level.getBlockState(targetPos);
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
InteractionResult r = state2.use(level, player, hand, hit);
if (r.consumesAction()) {
return;
}
} else {
// 无可选邻居
}
}
} else {
// 双手占用则跳过兜底交互
}
// 若邻居未提供 MenuProvider则跳过兜底交互1.21 API 变更避免不兼容的 use 调用
context.setPacketHandled(true);
});
}
}

View File

@ -1,20 +1,47 @@
package com.extendedae_plus.network;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.client.ui.ProviderSelectScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.NetworkEvent;
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.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* S2C: 返回可见且有空位的样板供应器列表客户端弹窗展示供用户选择
*/
public class ProvidersListS2CPacket {
public class ProvidersListS2CPacket implements CustomPacketPayload {
public static final Type<ProvidersListS2CPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "providers_list"));
public static final StreamCodec<RegistryFriendlyByteBuf, ProvidersListS2CPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> {
buf.writeVarInt(pkt.ids.size());
for (int i = 0; i < pkt.ids.size(); i++) {
buf.writeLong(pkt.ids.get(i));
buf.writeUtf(pkt.names.get(i));
buf.writeVarInt(pkt.emptySlots.get(i));
}
},
buf -> {
int size = buf.readVarInt();
List<Long> ids = new ArrayList<>(size);
List<String> names = new ArrayList<>(size);
List<Integer> slots = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ids.add(buf.readLong());
names.add(buf.readUtf());
slots.add(buf.readVarInt());
}
return new ProvidersListS2CPacket(ids, names, slots);
}
);
private final List<Long> ids;
private final List<String> names;
private final List<Integer> emptySlots;
@ -25,39 +52,17 @@ public class ProvidersListS2CPacket {
this.emptySlots = emptySlots;
}
public static void encode(ProvidersListS2CPacket msg, FriendlyByteBuf buf) {
buf.writeVarInt(msg.ids.size());
for (int i = 0; i < msg.ids.size(); i++) {
buf.writeLong(msg.ids.get(i));
buf.writeUtf(msg.names.get(i));
buf.writeVarInt(msg.emptySlots.get(i));
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static ProvidersListS2CPacket decode(FriendlyByteBuf buf) {
int size = buf.readVarInt();
List<Long> ids = new ArrayList<>(size);
List<String> names = new ArrayList<>(size);
List<Integer> slots = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ids.add(buf.readLong());
names.add(buf.readUtf());
slots.add(buf.readVarInt());
}
return new ProvidersListS2CPacket(ids, names, slots);
}
public static void handle(ProvidersListS2CPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
ctx.enqueueWork(() -> handleClient(msg));
ctx.setPacketHandled(true);
}
@OnlyIn(Dist.CLIENT)
private static void handleClient(ProvidersListS2CPacket msg) {
var mc = Minecraft.getInstance();
if (mc == null) return;
var current = mc.screen;
mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots));
public static void handle(final ProvidersListS2CPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
var mc = Minecraft.getInstance();
if (mc == null) return;
var current = mc.screen;
mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots));
});
}
}

View File

@ -3,30 +3,40 @@ package com.extendedae_plus.network;
import appeng.helpers.patternprovider.PatternContainer;
import appeng.menu.implementations.PatternAccessTermMenu;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* C2S: 请求当前终端可见的样板供应器列表用于弹窗选择
*/
public class RequestProvidersListC2SPacket {
public class RequestProvidersListC2SPacket implements CustomPacketPayload {
public static final Type<RequestProvidersListC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "request_providers_list"));
public static final RequestProvidersListC2SPacket INSTANCE = new RequestProvidersListC2SPacket();
public static final StreamCodec<FriendlyByteBuf, RequestProvidersListC2SPacket> STREAM_CODEC =
StreamCodec.unit(INSTANCE);
public RequestProvidersListC2SPacket() {}
public static void encode(RequestProvidersListC2SPacket msg, FriendlyByteBuf buf) {}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static RequestProvidersListC2SPacket decode(FriendlyByteBuf buf) { return new RequestProvidersListC2SPacket(); }
public static void handle(RequestProvidersListC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
public static void handle(final RequestProvidersListC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
if (player == null) return;
if (!(ctx.player() instanceof ServerPlayer player)) return;
if (!(player.containerMenu instanceof PatternEncodingTermMenu encMenu)) return;
// 优先若玩家也打开了样板访问终端则用 byId 方式精确服务器ID
@ -47,7 +57,7 @@ public class RequestProvidersListC2SPacket {
slots.add(empty);
}
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(filteredIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
player.connection.send(new ProvidersListS2CPacket(filteredIds, names, slots));
return;
}
@ -66,8 +76,7 @@ public class RequestProvidersListC2SPacket {
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
slots.add(empty);
}
ModNetwork.CHANNEL.sendTo(new ProvidersListS2CPacket(idxIds, names, slots), player.connection.connection, net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT);
player.connection.send(new ProvidersListS2CPacket(idxIds, names, slots));
});
ctx.setPacketHandled(true);
}
}

View File

@ -1,57 +1,60 @@
package com.extendedae_plus.network;
import appeng.menu.SlotSemantics;
import com.extendedae_plus.ExtendedAEPlus;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
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.neoforged.neoforge.network.handling.IPayloadContext;
import java.lang.reflect.Field;
import java.util.function.Supplier;
/**
* S2C: 指示客户端在已打开的样板供应器界面切换到指定页
*/
public class SetProviderPageS2CPacket {
public class SetProviderPageS2CPacket implements CustomPacketPayload {
public static final Type<SetProviderPageS2CPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "set_provider_page"));
public static final StreamCodec<RegistryFriendlyByteBuf, SetProviderPageS2CPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> buf.writeVarInt(pkt.page),
buf -> new SetProviderPageS2CPacket(buf.readVarInt())
);
private final int page;
public SetProviderPageS2CPacket(int page) {
this.page = page;
}
public static void encode(SetProviderPageS2CPacket msg, FriendlyByteBuf buf) {
buf.writeVarInt(msg.page);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static SetProviderPageS2CPacket decode(FriendlyByteBuf buf) {
int p = buf.readVarInt();
return new SetProviderPageS2CPacket(p);
}
public static void handle(SetProviderPageS2CPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
public static void handle(final SetProviderPageS2CPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
try {
Screen screen = Minecraft.getInstance().screen;
if (screen instanceof GuiExPatternProvider guiExPatternProvider) {
Field currentPage = screen.getClass().getDeclaredField("eap$currentPage");
currentPage.setAccessible(true);
currentPage.setInt(guiExPatternProvider, msg.page);
try {
Screen screen = Minecraft.getInstance().screen;
if (screen instanceof GuiExPatternProvider guiExPatternProvider) {
Field currentPage = screen.getClass().getDeclaredField("eap$currentPage");
currentPage.setAccessible(true);
currentPage.setInt(guiExPatternProvider, msg.page);
guiExPatternProvider.repositionSlots(SlotSemantics.ENCODED_PATTERN);
guiExPatternProvider.repositionSlots(SlotSemantics.STORAGE);
guiExPatternProvider.repositionSlots(SlotSemantics.ENCODED_PATTERN);
guiExPatternProvider.repositionSlots(SlotSemantics.STORAGE);
Field hs = screen.getClass().getDeclaredField("hoveredSlot");
hs.setAccessible(true);
hs.set(screen, null);
}
} catch (Throwable ignored) {
}
Field hs = screen.getClass().getDeclaredField("hoveredSlot");
hs.setAccessible(true);
hs.set(screen, null);
}
);
ctx.setPacketHandled(true);
} catch (Throwable ignored) {
}
});
}
}

View File

@ -3,34 +3,37 @@ package com.extendedae_plus.network;
import appeng.menu.me.items.PatternEncodingTermMenu;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/**
* C2S: 请求将图样编码终端的已编码样板上传到指定的样板供应器由客户端选择
*/
public class UploadEncodedPatternToProviderC2SPacket {
public class UploadEncodedPatternToProviderC2SPacket implements CustomPacketPayload {
public static final Type<UploadEncodedPatternToProviderC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(com.extendedae_plus.ExtendedAEPlus.MODID, "upload_pattern_to_provider"));
public static final StreamCodec<FriendlyByteBuf, UploadEncodedPatternToProviderC2SPacket> STREAM_CODEC = StreamCodec.of(
(buf, pkt) -> buf.writeLong(pkt.providerId),
buf -> new UploadEncodedPatternToProviderC2SPacket(buf.readLong())
);
private final long providerId;
public UploadEncodedPatternToProviderC2SPacket(long providerId) {
this.providerId = providerId;
}
public static void encode(UploadEncodedPatternToProviderC2SPacket msg, FriendlyByteBuf buf) {
buf.writeLong(msg.providerId);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static UploadEncodedPatternToProviderC2SPacket decode(FriendlyByteBuf buf) {
return new UploadEncodedPatternToProviderC2SPacket(buf.readLong());
}
public static void handle(UploadEncodedPatternToProviderC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
public static void handle(final UploadEncodedPatternToProviderC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
if (player == null) return;
if (!(ctx.player() instanceof ServerPlayer player)) return;
if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
// 支持两种模式
// 1) providerId >= 0: 访问终端 byId 模式
@ -42,6 +45,5 @@ public class UploadEncodedPatternToProviderC2SPacket {
ExtendedAEPatternUploadUtil.uploadFromEncodingMenuToProviderByIndex(player, menu, index);
}
});
ctx.setPacketHandled(true);
}
}

View File

@ -6,6 +6,8 @@ import appeng.api.inventories.InternalInventory;
import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode;
import appeng.core.definitions.AEItems;
import appeng.menu.AEBaseMenu;
import appeng.api.networking.security.IActionHost;
import appeng.crafting.pattern.AECraftingPattern;
import appeng.crafting.pattern.AESmithingTablePattern;
import appeng.crafting.pattern.AEStonecuttingPattern;
@ -27,9 +29,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.items.IItemHandler;
import net.neoforged.fml.loading.FMLPaths;
import java.io.IOException;
import java.lang.reflect.Field;
@ -102,8 +102,10 @@ public class ExtendedAEPatternUploadUtil {
if (k.contains(":")) {
// 形如 namespace:path
try {
ResourceLocation rl = new ResourceLocation(k);
map.put(rl, name);
var rl = ResourceLocation.tryParse(k);
if (rl != null) {
map.put(rl, name);
}
} catch (Exception ignored) {}
} else {
// 视为别名最终搜索关键字大小写不敏感
@ -163,8 +165,10 @@ public class ExtendedAEPatternUploadUtil {
// 更新内存映射
if (key.contains(":")) {
try {
ResourceLocation rl = new ResourceLocation(key);
CUSTOM_NAMES.put(rl, cnValue);
var rl = ResourceLocation.tryParse(key);
if (rl != null) {
CUSTOM_NAMES.put(rl, cnValue);
}
} catch (Exception ignored) {}
} else {
CUSTOM_ALIASES.put(key.toLowerCase(), cnValue);
@ -216,11 +220,12 @@ public class ExtendedAEPatternUploadUtil {
for (String k : toRemove) {
if (k.contains(":")) {
try {
ResourceLocation rl = new ResourceLocation(k);
// 仅当值匹配才移除双重保险
String cur = CUSTOM_NAMES.get(rl);
if (target.equals(cur)) {
CUSTOM_NAMES.remove(rl);
var rl = ResourceLocation.tryParse(k);
if (rl != null) {
String cur = CUSTOM_NAMES.get(rl);
if (target.equals(cur)) {
CUSTOM_NAMES.remove(rl);
}
}
} catch (Exception ignored) {}
} else {
@ -287,32 +292,7 @@ public class ExtendedAEPatternUploadUtil {
return key.getPath();
}
/**
* GTCEu GTRecipe -> 搜索关键字
* 优先自定义中文映射其次使用注册ID的 path最后回退到完整ID字符串
*/
public static String mapGTCEuRecipeToSearchKey(com.gregtechceu.gtceu.api.recipe.GTRecipe gtRecipe) {
if (gtRecipe == null) return null;
try {
// GTRecipeType.toString() 返回 registryName.toString() namespace:path
String idStr = String.valueOf(gtRecipe.getType());
if (idStr == null || idStr.isBlank()) return null;
ResourceLocation rl = new ResourceLocation(idStr);
// 1) 先查别名使用 path 作为最终搜索关键字
String path = rl.getPath();
if (path != null) {
String alias = CUSTOM_ALIASES.get(path.toLowerCase());
if (alias != null && !alias.isBlank()) return alias;
}
// 2) 再查完整ID映射
String custom = CUSTOM_NAMES.get(rl);
if (custom != null && !custom.isBlank()) return custom;
// 3) 默认返回 path 作为搜索关键字
return (path != null && !path.isBlank()) ? path : idStr;
} catch (Throwable t) {
return null;
}
}
// 注意GTCEu 的映射方法已在下方提供基于 Object 的反射版本避免重复定义
/**
* 仅使用反射的 GTCEu GTRecipe -> 搜索关键字避免在运行时直接引用 GTCEu
@ -326,15 +306,15 @@ public class ExtendedAEPatternUploadUtil {
Object typeObj = mGetType.invoke(gtRecipeObj);
String idStr = String.valueOf(typeObj);
if (idStr == null || idStr.isBlank()) return null;
ResourceLocation rl = new ResourceLocation(idStr);
var rl = ResourceLocation.tryParse(idStr);
// 1) 别名优先使用 path 作为最终搜索关键字
String path = rl.getPath();
String path = rl != null ? rl.getPath() : null;
if (path != null) {
String alias = CUSTOM_ALIASES.get(path.toLowerCase());
if (alias != null && !alias.isBlank()) return alias;
}
// 2) 再查完整ID映射
String custom = CUSTOM_NAMES.get(rl);
String custom = rl != null ? CUSTOM_NAMES.get(rl) : null;
if (custom != null && !custom.isBlank()) return custom;
// 3) 默认返回 path 作为搜索关键字
return (path != null && !path.isBlank()) ? path : idStr;
@ -445,12 +425,15 @@ public class ExtendedAEPatternUploadUtil {
}
// 获取 AE 网络
IGridNode node = menu.getNetworkNode();
if (node == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
return false;
}
IGrid grid = node.getGrid();
IGrid grid = null;
try {
if (menu instanceof AEBaseMenu abm) {
Object target = abm.getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) {
grid = host.getActionableNode().getGrid();
}
}
} catch (Throwable ignored) {}
if (grid == null) {
sendMessage(player, "ExtendedAE Plus: 当前不在有效的 AE 网络中");
return false;
@ -506,7 +489,7 @@ public class ExtendedAEPatternUploadUtil {
}
// 回退尝试 Forge 能力可能为聚合图样仓同样遍历所有矩阵
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
List<?> handlers = findAllMatrixPatternHandlers(grid);
if (!handlers.isEmpty()) {
for (int i = 0; i < handlers.size(); i++) {
var cap = handlers.get(i);
@ -559,44 +542,11 @@ public class ExtendedAEPatternUploadUtil {
/**
* 在给定 AE Grid 中收集所有已成型的装配矩阵的聚合图样仓 IItemHandler若可用
*/
private static List<IItemHandler> findAllMatrixPatternHandlers(IGrid grid) {
List<IItemHandler> result = new ArrayList<>();
try {
Set<TileAssemblerMatrixBase> matrices = grid.getMachines(TileAssemblerMatrixBase.class);
int idx = 0;
for (TileAssemblerMatrixBase tile : matrices) {
if (tile != null && tile.isFormed()) {
var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null);
if (capOpt != null) {
var handler = capOpt.orElse(null);
if (handler != null) {
result.add(handler);
}
}
}
idx++;
}
} catch (Throwable ignored) {
}
return result;
private static List<?> findAllMatrixPatternHandlers(IGrid grid) {
// NeoForge 1.21 能力系统与 API 变更此处先返回空列表避免编译期依赖旧能力系统
return java.util.Collections.emptyList();
}
/**
* 尝试将整个物品栈插入到 IItemHandler 的任意槽位返回剩余物品
*/
private static ItemStack insertIntoAnySlot(IItemHandler handler, ItemStack stack) {
ItemStack remaining = stack.copy();
if (handler == null || remaining.isEmpty()) return remaining;
for (int i = 0; i < handler.getSlots(); i++) {
remaining = handler.insertItem(i, remaining, false);
if (remaining.isEmpty()) break;
}
return remaining;
}
/**
* 检查装配矩阵所有已成型矩阵的图样仓中是否已存在与给定样板完全相同的物品含NBT
*/
private static boolean matrixContainsPattern(IGrid grid, ItemStack pattern) {
if (grid == null || pattern == null || pattern.isEmpty()) return false;
try {
@ -606,31 +556,24 @@ public class ExtendedAEPatternUploadUtil {
if (inv == null) continue;
for (int i = 0; i < inv.size(); i++) {
ItemStack s = inv.getStackInSlot(i);
if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
try {
// 再检查聚合能力视图
List<IItemHandler> handlers = findAllMatrixPatternHandlers(grid);
for (IItemHandler h : handlers) {
if (h == null) continue;
int slots = h.getSlots();
for (int i = 0; i < slots; i++) {
ItemStack s = h.getStackInSlot(i);
if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameTags(s, pattern)) {
if (!s.isEmpty() && net.minecraft.world.item.ItemStack.isSameItemSameComponents(s, pattern)) {
return true;
}
}
}
} catch (Throwable t) {
}
// 1.21 暂不检查聚合能力视图能力系统适配后再补充
return false;
}
/**
* 能力系统IItemHandler未迁移前的占位插入直接返回原始栈表示未能插入
*/
private static ItemStack insertIntoAnySlot(Object handler, ItemStack stack) {
return stack.copy();
}
/**
* 检查当前菜单是否为ExtendedAE的扩展样板管理终端
*
@ -987,12 +930,16 @@ public class ExtendedAEPatternUploadUtil {
return false;
}
// 获取 AE 网络
IGridNode node = menu.getNetworkNode();
if (node == null) {
return false;
}
IGrid grid = node.getGrid();
// 获取 AE 网络1.21 经由 AEBaseMenu target + IActionHost
IGrid grid = null;
try {
if (menu instanceof AEBaseMenu abm) {
Object target = abm.getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) {
grid = host.getActionableNode().getGrid();
}
}
} catch (Throwable ignored) {}
if (grid == null) {
return false;
}
@ -1131,10 +1078,14 @@ public class ExtendedAEPatternUploadUtil {
List<PatternContainer> list = new ArrayList<>();
if (menu == null) return list;
try {
IGridNode node = menu.getNetworkNode();
if (node == null) return list;
IGrid grid = node.getGrid();
if (grid == null) return list;
IGrid grid = null;
if (menu instanceof AEBaseMenu abm) {
Object target = abm.getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) {
grid = host.getActionableNode().getGrid();
}
}
if (grid == null) return list;
for (var machineClass : grid.getMachineClasses()) {
if (PatternContainer.class.isAssignableFrom(machineClass)) {
@SuppressWarnings("unchecked")