fix: 修复供应器高亮在服务器不显示的问题,优化结构
This commit is contained in:
parent
1f16460372
commit
a4b3306ad9
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user