fix: 修复供应器高亮在服务器不显示的问题,优化结构

This commit is contained in:
C-H716 2025-10-10 20:45:44 +08:00
parent 1f16460372
commit a4b3306ad9
3 changed files with 226 additions and 108 deletions

View File

@ -72,6 +72,12 @@ public class ModNetwork {
.consumerNetworkThread(SetPatternHighlightS2CPacket::handle)
.add();
CHANNEL.messageBuilder(com.extendedae_plus.network.SetBlockHighlightS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(com.extendedae_plus.network.SetBlockHighlightS2CPacket::encode)
.decoder(com.extendedae_plus.network.SetBlockHighlightS2CPacket::decode)
.consumerNetworkThread(com.extendedae_plus.network.SetBlockHighlightS2CPacket::handle)
.add();
CHANNEL.messageBuilder(SetProviderPageS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(SetProviderPageS2CPacket::encode)
.decoder(SetProviderPageS2CPacket::decode)

View File

@ -0,0 +1,77 @@
package com.extendedae_plus.network;
import com.glodblock.github.extendedae.client.render.EAEHighlightHandler;
import com.glodblock.github.extendedae.util.FCClientUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;
/**
* S2C: 指示客户端对某个方块位置进行高亮仅作用于接收该包的客户端
*/
public class SetBlockHighlightS2CPacket {
private final BlockPos pos;
private final Direction face; // 可为 null表示方块形
private final ResourceLocation dim; // 维度的 ResourceLocation
private final long durationMillis; // 持续时间毫秒
public SetBlockHighlightS2CPacket(BlockPos pos, Direction face, ResourceLocation dim, long durationMillis) {
this.pos = pos;
this.face = face;
this.dim = dim;
this.durationMillis = durationMillis;
}
public static void encode(SetBlockHighlightS2CPacket pkt, FriendlyByteBuf buf) {
buf.writeBlockPos(pkt.pos);
buf.writeBoolean(pkt.face != null);
if (pkt.face != null) buf.writeEnum(pkt.face);
buf.writeResourceLocation(pkt.dim);
buf.writeLong(pkt.durationMillis);
}
public static SetBlockHighlightS2CPacket decode(FriendlyByteBuf buf) {
var pos = buf.readBlockPos();
Direction face = null;
if (buf.readBoolean()) face = buf.readEnum(Direction.class);
var dim = buf.readResourceLocation();
long dur = buf.readLong();
return new SetBlockHighlightS2CPacket(pos, face, dim, dur);
}
public static void handle(SetBlockHighlightS2CPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
try {
// 在客户端执行高亮
ResourceKey<Level> dimKey = ResourceKey.create(Registries.DIMENSION, msg.dim);
long endTime = System.currentTimeMillis() + msg.durationMillis;
if (msg.face == null) {
EAEHighlightHandler.highlight(msg.pos, dimKey, endTime);
} else {
var origin = new AABB(msg.pos);
switch (msg.face) {
case WEST -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) (Math.PI / 2));
case SOUTH -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) Math.PI);
case EAST -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) (-Math.PI / 2));
case UP -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.X, (float) (-Math.PI / 2));
case DOWN -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.X, (float) (Math.PI / 2));
}
EAEHighlightHandler.highlight(msg.pos, msg.face, dimKey, endTime, origin);
}
} catch (Throwable ignored) {
}
});
ctx.setPacketHandled(true);
}
}

View File

@ -5,41 +5,34 @@ import appeng.api.networking.IGrid;
import appeng.api.networking.security.IActionHost;
import appeng.api.stacks.AEKey;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.me.service.CraftingService;
import appeng.menu.AEBaseMenu;
import appeng.menu.locator.MenuLocators;
import appeng.menu.me.crafting.CraftingCPUMenu;
import appeng.parts.AEBasePart;
import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
import com.extendedae_plus.network.SetBlockHighlightS2CPacket;
import com.extendedae_plus.network.SetPatternHighlightS2CPacket;
import com.extendedae_plus.network.provider.SetProviderPageS2CPacket;
import com.extendedae_plus.util.PatternProviderDataUtil;
import com.glodblock.github.extendedae.util.FCClientUtil;
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.chat.Component;
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 java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;
import static com.glodblock.github.extendedae.client.render.EAEHighlightHandler.highlight;
/**
* 客户端从 CraftingCPUScreen 发送鼠标下条目对应的 AEKey
* 服务端在当前打开的 CraftingCPUMenu 所属网络中定位匹配该 AEKey 的样板供应器
* 打开该供应器自身的 UI不是目标机器的 UI
* 网络包客户端向服务器发送请求打开与 AEKey 对应的 PatternProvider UI
* <p>
* 流程
* 1. 客户端发送 AEKey
* 2. 服务端根据当前 CraftingCPUMenu 获取对应 Grid
* 3. 定位所有提供该 AEKey PatternProvider
* 4. 打开 Provider UI并向客户端发送高亮和页码信息
*/
public class CraftingMonitorOpenProviderC2SPacket {
private final AEKey what;
@ -53,118 +46,160 @@ public class CraftingMonitorOpenProviderC2SPacket {
}
public static CraftingMonitorOpenProviderC2SPacket decode(FriendlyByteBuf buf) {
AEKey key = AEKey.readKey(buf);
return new CraftingMonitorOpenProviderC2SPacket(key);
return new CraftingMonitorOpenProviderC2SPacket(AEKey.readKey(buf));
}
public static void handle(CraftingMonitorOpenProviderC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get();
var context = ctx.get();
context.enqueueWork(() -> {
ServerPlayer player = context.getSender();
if (player == null) return;
if (player == null || !(player.containerMenu instanceof CraftingCPUMenu menu)) return;
// 必须在 CraftingCPU 界面内
if (!(player.containerMenu instanceof CraftingCPUMenu menu)) {
return;
}
// 通过菜单的 target可能是 BlockEntity/Part/ItemHost IActionHost 获取 Grid
IGrid grid = null;
Object target = ((AEBaseMenu) menu).getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) {
grid = host.getActionableNode().getGrid();
}
if (grid == null) {
return;
}
// 从菜单获取 Grid
IGrid grid = GridHelper.getGridFromMenu(menu);
if (grid == null) return;
// 获取 CraftingService
var cs = grid.getCraftingService();
if (!(cs instanceof CraftingService craftingService)) {
return;
}
if (!(cs instanceof CraftingService craftingService)) return;
// 1) 根据 AEKey 找到可能的样板pattern
// 根据 AEKey 查找所有匹配样板
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what);
if (patterns.isEmpty()) {
return;
}
if (patterns.isEmpty()) return;
// 2) 遍历提供该样板的 Provider定位 PatternProviderLogic
// 遍历所有样板找到第一个可用 Provider 并打开 UI
for (var pattern : patterns) {
var providers = craftingService.getProviders(pattern);
for (var provider : providers) {
if (provider instanceof PatternProviderLogic ppl) {
// accessor 获取 host
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
if (host == null) continue;
var pbe = host.getBlockEntity();
if (pbe == null) continue;
var provider = PatternLocator.findValidProvider(craftingService, pattern, grid);
if (provider == null) continue;
// 跳过未连接到网格或不活跃的 provider使用 util 判断并传入当前 grid
if (!PatternProviderDataUtil.isProviderAvailable(ppl, grid)) continue;
// 直接打开供应器自身的 UI调用 Host 默认方法
try {
// 部件与方块实体分别选择定位器并打开界面
if (host instanceof AEBasePart part) {
host.openMenu(player, MenuLocators.forPart(part));
highlightWithMessage(pbe.getBlockPos(), part.getSide(), Objects.requireNonNull(pbe.getLevel()).dimension(), 1.0, player);
} else {
host.openMenu(player, MenuLocators.forBlockEntity(pbe));
highlightWithMessage(pbe.getBlockPos(), null, Objects.requireNonNull(pbe.getLevel()).dimension(), 1.0, player);
}
// 高亮打开的供应器位置并发送聊天提示
// 先在该 provider 中定位 pattern 的槽位索引以便计算页码尽量早退出按槽位逐个解码
int foundSlot = PatternProviderDataUtil.findSlotForPattern(ppl, pattern.getDefinition());
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);
}
}
// 最后发送高亮包保证界面已打开
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);
}
return;
} catch (Exception ignored) {
}
}
}
try {
ProviderUIHelper.openProviderUI(provider, pattern, player);
} catch (Exception ignored) {}
}
});
context.setPacketHandled(true);
}
private static void highlightWithMessage(BlockPos pos, Direction face, ResourceKey<Level> dim, double multiplier, Player player) {
if (pos == null || dim == null) {
return;
}
long endTime = System.currentTimeMillis() + (long) (6000 * GlodUtil.clamp(multiplier, 1, 30));
if (face == null) {
highlight(pos, dim, endTime);
} else {
var origin = new AABB(2 / 16D, 2 / 16D, 0, 14 / 16D, 14 / 16D, 2 / 16D).move(pos);
var center = new AABB(pos).getCenter();
switch (face) {
case WEST -> origin = FCClientUtil.rotor(origin, center, Direction.Axis.Y, (float) (Math.PI / 2));
case SOUTH -> origin = FCClientUtil.rotor(origin, center, Direction.Axis.Y, (float) Math.PI);
case EAST -> origin = FCClientUtil.rotor(origin, center, Direction.Axis.Y, (float) (-Math.PI / 2));
case UP -> origin = FCClientUtil.rotor(origin, center, Direction.Axis.X, (float) (-Math.PI / 2));
case DOWN -> origin = FCClientUtil.rotor(origin, center, Direction.Axis.X, (float) (Math.PI / 2));
}
highlight(pos, face, dim, endTime, origin);
}
// ===================== 内部工具类 =====================
if (player != null) {
player.displayClientMessage(Component.translatable("chat.ex_pattern_access_terminal.pos", pos.toShortString(), dim.location().getPath()), false);
/**
* GridHelper: 从菜单中获取网格实例
*/
public static final class GridHelper {
private GridHelper() {}
/**
* 获取菜单对应的 Grid
* @param menu 当前 AEBaseMenu
* @return Grid null
*/
public static IGrid getGridFromMenu(appeng.menu.AEBaseMenu menu) {
Object target = menu.getTarget();
if (target instanceof IActionHost host && host.getActionableNode() != null) {
return host.getActionableNode().getGrid();
}
return null;
}
}
/**
* PatternLocator: 根据样板定位可用的 Provider
*/
public static final class PatternLocator {
private PatternLocator() {}
/**
* 查找提供指定样板的可用 Provider
* @param cs CraftingService
* @param pattern 样板
* @param grid 当前 Grid
* @return 第一个可用的 PatternProviderLogic null
*/
public static PatternProviderLogic findValidProvider(CraftingService cs, IPatternDetails pattern, IGrid grid) {
var providers = cs.getProviders(pattern);
for (var provider : providers) {
if (provider instanceof PatternProviderLogic ppl) {
var host = ((PatternProviderLogicAccessor) ppl).eap$host();
if (host == null || host.getBlockEntity() == null) continue;
if (!PatternProviderDataUtil.isProviderAvailable(ppl, grid)) continue;
return ppl;
}
}
return null;
}
}
/**
* ProviderUIHelper: 打开 Provider UI 并发送客户端反馈
*/
public static final class ProviderUIHelper {
private ProviderUIHelper() {}
/**
* 打开 Provider UI
* 1. 打开菜单
* 2. 发送高亮包
* 3. 发送页码包
* 4. 发送 Pattern 输出高亮包
*
* @param provider PatternProviderLogic 实例
* @param pattern 样板
* @param player 玩家
*/
public static void openProviderUI(PatternProviderLogic provider, IPatternDetails pattern, ServerPlayer player) {
var host = ((PatternProviderLogicAccessor) provider).eap$host();
var pbe = host.getBlockEntity();
if (pbe == null) return;
boolean isPart = host instanceof AEBasePart part;
var locator = isPart ? MenuLocators.forPart((AEBasePart) host) : MenuLocators.forBlockEntity(pbe);
host.openMenu(player, locator);
// 高亮显示
ModNetwork.CHANNEL.sendTo(
new SetBlockHighlightS2CPacket(
pbe.getBlockPos(),
isPart ? ((AEBasePart) host).getSide() : null,
pbe.getLevel().dimension().location(),
(long) (6000 * GlodUtil.clamp(1.0, 1, 30))
),
player.connection.connection,
NetworkDirection.PLAY_TO_CLIENT
);
// 聊天提示
player.displayClientMessage(
Component.translatable(
"chat.ex_pattern_access_terminal.pos",
pbe.getBlockPos().toShortString(),
pbe.getLevel().dimension().location().getPath()
),
false
);
// 页码同步
int slot = PatternProviderDataUtil.findSlotForPattern(provider, pattern.getDefinition());
if (slot >= 0) {
int page = slot / 36;
if (page > 0) {
ModNetwork.CHANNEL.sendTo(
new SetProviderPageS2CPacket(page),
player.connection.connection,
NetworkDirection.PLAY_TO_CLIENT
);
}
}
// 输出高亮
var outputs = pattern.getOutputs();
if (outputs != null && outputs.length > 0 && outputs[0] != null) {
AEKey key = outputs[0].what();
ModNetwork.CHANNEL.sendTo(
new SetPatternHighlightS2CPacket(key, true),
player.connection.connection,
NetworkDirection.PLAY_TO_CLIENT
);
}
}
}
}