From 0622767f10d544e9249e1385a99789adcf0125b1 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:04:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=89=B3=E6=89=8B=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E6=97=A0=E7=BA=BF=E6=94=B6=E5=8F=91=E5=99=A8=E9=A2=91?= =?UTF-8?q?=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/ui/FrequencyInputScreen.java | 209 ++++++++++++++++++ .../WirelessTransceiverBlockEntity.java | 15 ++ .../com/extendedae_plus/hooks/WrenchHook.java | 41 +++- .../com/extendedae_plus/init/ModNetwork.java | 4 + .../SetWirelessFrequencyC2SPacket.java | 89 ++++++++ .../assets/extendedae_plus/lang/en_us.json | 7 + .../assets/extendedae_plus/lang/zh_cn.json | 7 + 7 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java create mode 100644 src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java diff --git a/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java b/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java new file mode 100644 index 0000000..e8385cd --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java @@ -0,0 +1,209 @@ +package com.extendedae_plus.client.ui; + +import com.extendedae_plus.network.SetWirelessFrequencyC2SPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.neoforged.neoforge.network.PacketDistributor; + +/** + * 频率输入GUI界面 + * 用于设置无线收发器的频率 + * + * API变化说明: + * 1. @OnlyIn(Dist.CLIENT)在1.21.1中已移除,不再需要 + * 2. GuiGraphics替代了PoseStack作为渲染参数(1.20+的变化) + * 3. 网络数据包通过PacketDistributor发送 + * 4. EditBox的API在1.21.1中保持稳定 + */ +public class FrequencyInputScreen extends Screen { + + private static final int WINDOW_WIDTH = 200; + private static final int WINDOW_HEIGHT = 80; + + private final BlockPos pos; + private final long currentFrequency; + private EditBox frequencyInput; + private Button confirmButton; + + /** + * 构造函数 + * @param pos 无线收发器的位置 + * @param currentFrequency 当前频率 + */ + public FrequencyInputScreen(BlockPos pos, long currentFrequency) { + super(Component.translatable("gui.extendedae_plus.frequency_input.title")); + this.pos = pos; + this.currentFrequency = currentFrequency; + } + + @Override + protected void init() { + super.init(); + + // 计算居中位置 + int x = (this.width - WINDOW_WIDTH) / 2; + int y = (this.height - WINDOW_HEIGHT) / 2; + + // 创建输入框 + // API说明:EditBox构造函数参数:font, x, y, width, height, component + this.frequencyInput = new EditBox( + this.font, + x + 10, + y + 30, + WINDOW_WIDTH - 20, + 20, + Component.translatable("gui.extendedae_plus.frequency_input.field") + ); + + // 设置输入框属性 + this.frequencyInput.setMaxLength(19); // long类型最大19位数字 + this.frequencyInput.setValue(String.valueOf(currentFrequency)); + this.frequencyInput.setFilter(this::isValidInput); // 只允许数字和负号 + this.frequencyInput.setFocused(true); + + // 添加输入框到组件列表 + this.addRenderableWidget(this.frequencyInput); + + // 创建确认按钮 + // API说明:Button.builder方法在1.21.1中使用 + this.confirmButton = Button.builder( + Component.translatable("gui.extendedae_plus.frequency_input.confirm"), + button -> this.onConfirm() + ) + .bounds(x + 10, y + 55, 80, 20) + .build(); + + this.addRenderableWidget(this.confirmButton); + + // 创建取消按钮 + Button cancelButton = Button.builder( + Component.translatable("gui.extendedae_plus.frequency_input.cancel"), + button -> this.onClose() + ) + .bounds(x + 110, y + 55, 80, 20) + .build(); + + this.addRenderableWidget(cancelButton); + } + + /** + * 输入验证:只允许数字和负号 + */ + private boolean isValidInput(String input) { + if (input.isEmpty()) { + return true; + } + // 允许负号在开头 + if (input.equals("-")) { + return true; + } + try { + Long.parseLong(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 按键处理:回车键确认,ESC键取消 + * + * API说明:keyPressed方法在1.21.1中保持一致 + */ + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + // 回车键确认 + if (keyCode == 257 || keyCode == 335) { // ENTER or NUMPAD_ENTER + this.onConfirm(); + return true; + } + // ESC键取消 + if (keyCode == 256) { // ESC + this.onClose(); + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + /** + * 渲染背景 + * + * API变化说明: + * - GuiGraphics替代了旧的PoseStack + BufferSource组合 + * - renderBackground方法签名在1.21.1中简化 + */ + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + // 渲染暗色背景 + super.render(guiGraphics, mouseX, mouseY, partialTick); + + // 计算窗口位置 + int x = (this.width - WINDOW_WIDTH) / 2; + int y = (this.height - WINDOW_HEIGHT) / 2; + + // 绘制窗口背景 + guiGraphics.fill(x, y, x + WINDOW_WIDTH, y + WINDOW_HEIGHT, 0xC0000000); + + // 绘制窗口边框 + guiGraphics.fill(x, y, x + WINDOW_WIDTH, y + 1, 0xFFFFFFFF); // 顶部 + guiGraphics.fill(x, y + WINDOW_HEIGHT - 1, x + WINDOW_WIDTH, y + WINDOW_HEIGHT, 0xFFFFFFFF); // 底部 + guiGraphics.fill(x, y, x + 1, y + WINDOW_HEIGHT, 0xFFFFFFFF); // 左侧 + guiGraphics.fill(x + WINDOW_WIDTH - 1, y, x + WINDOW_WIDTH, y + WINDOW_HEIGHT, 0xFFFFFFFF); // 右侧 + + // 绘制标题 + Component title = Component.translatable("gui.extendedae_plus.frequency_input.title"); + guiGraphics.drawString( + this.font, + title, + x + (WINDOW_WIDTH - this.font.width(title)) / 2, + y + 10, + 0xFFFFFFFF, + false + ); + } + + /** + * 确认按钮处理 + */ + private void onConfirm() { + String input = this.frequencyInput.getValue(); + if (input.isEmpty()) { + this.onClose(); + return; + } + + try { + long frequency = Long.parseLong(input); + + // 发送数据包到服务端 + // API说明:NeoForge使用PacketDistributor.sendToServer + PacketDistributor.sendToServer(new SetWirelessFrequencyC2SPacket(pos, frequency)); + + this.onClose(); + } catch (NumberFormatException e) { + // 输入无效,不做处理 + } + } + + /** + * 暂停游戏状态 + * 返回false表示不暂停游戏(允许多人游戏中正常使用) + */ + @Override + public boolean isPauseScreen() { + return false; + } + + /** + * 静态工厂方法:打开频率输入界面 + */ + public static void open(BlockPos pos, long currentFrequency) { + Minecraft.getInstance().setScreen(new FrequencyInputScreen(pos, currentFrequency)); + } +} + diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java index 37a38b2..3c004e1 100644 --- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java +++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java @@ -156,6 +156,21 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements setChanged(); } + /** + * 强制设置频率,忽略锁定状态 + * 用于扳手GUI等管理工具 + */ + public void setFrequencyForced(long frequency) { + if (this.frequency == frequency) return; + this.frequency = frequency; + if (isMasterMode()) { + masterLink.setFrequency(frequency); + } else { + slaveLink.setFrequency(frequency); + } + setChanged(); + } + public boolean isMasterMode() { return masterMode; } diff --git a/src/main/java/com/extendedae_plus/hooks/WrenchHook.java b/src/main/java/com/extendedae_plus/hooks/WrenchHook.java index c5a4a22..1651fb8 100644 --- a/src/main/java/com/extendedae_plus/hooks/WrenchHook.java +++ b/src/main/java/com/extendedae_plus/hooks/WrenchHook.java @@ -2,6 +2,7 @@ package com.extendedae_plus.hooks; import appeng.util.InteractionUtil; import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.client.ui.FrequencyInputScreen; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; import appeng.block.crafting.CraftingUnitBlock; import appeng.blockentity.crafting.CraftingBlockEntity; @@ -14,6 +15,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; @@ -90,7 +92,7 @@ public final class WrenchHook { // 未潜行 + 扳手:切换锁定状态 BlockEntity be = level.getBlockEntity(hit.getBlockPos()); if (be instanceof WirelessTransceiverBlockEntity te) { - // 仅在服务端切换与同步,避免仅客户端生效导致看起来“无效果” + // 仅在服务端切换与同步,避免仅客户端生效导致看起来"无效果" if (!level.isClientSide) { boolean newLocked = !te.isLocked(); te.setLocked(newLocked); @@ -103,7 +105,9 @@ public final class WrenchHook { ExtendedAEPlus.LOGGER.debug("sendBlockUpdated failed: {}", t.toString()); } // 提示玩家(服务端消息下发到客户端) - player.displayClientMessage(Component.literal(newLocked ? "已锁定收发器" : "已解锁收发器"), true); + player.displayClientMessage(Component.translatable( + newLocked ? "extendedae_plus.wireless.locked" : "extendedae_plus.wireless.unlocked" + ), true); // 轻微反馈音效 level.playSound(player, hit.getBlockPos(), SoundEvents.LEVER_CLICK, SoundSource.BLOCKS, 0.5F, newLocked ? 0.6F : 0.9F); ExtendedAEPlus.LOGGER.debug("Wrench toggle lock at {} -> {}", pos, newLocked); @@ -116,4 +120,37 @@ public final class WrenchHook { } } } + + @SubscribeEvent + public static void onPlayerLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) { + // 潜行左键触发:打开频率设置GUI + if (event.isCanceled()) { + return; + } + + var player = event.getEntity(); + var level = event.getLevel(); + var pos = event.getPos(); + + // 非旁观者 + if (player.isSpectator()) { + return; + } + + ItemStack stack = player.getMainHandItem(); + + // 潜行 + 扳手:打开频率输入GUI + if (InteractionUtil.isInAlternateUseMode(player) && InteractionUtil.canWrenchRotate(stack)) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof WirelessTransceiverBlockEntity te) { + if (level.isClientSide) { + // 客户端:打开频率输入GUI + FrequencyInputScreen.open(pos, te.getFrequency()); + ExtendedAEPlus.LOGGER.debug("Opening frequency input GUI for transceiver at {}", pos); + } + + event.setCanceled(true); + } + } + } } diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 89e87ac..7c8c63b 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -38,5 +38,9 @@ public class ModNetwork { registrar.playToServer(com.extendedae_plus.network.ChannelCardBindPacket.TYPE, com.extendedae_plus.network.ChannelCardBindPacket.STREAM_CODEC, com.extendedae_plus.network.ChannelCardBindPacket::handle); + // 无线收发器频率设置 + registrar.playToServer(com.extendedae_plus.network.SetWirelessFrequencyC2SPacket.TYPE, + com.extendedae_plus.network.SetWirelessFrequencyC2SPacket.STREAM_CODEC, + com.extendedae_plus.network.SetWirelessFrequencyC2SPacket::handle); } } diff --git a/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java b/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java new file mode 100644 index 0000000..a9a35de --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java @@ -0,0 +1,89 @@ +package com.extendedae_plus.network; + +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; +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.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +/** + * C2S: 客户端发送到服务端,用于设置无线收发器的频率 + * + * API变化说明: + * - 1.20.1 Forge使用SimpleChannel.messageBuilder + * - 1.21.1 NeoForge使用CustomPacketPayload + StreamCodec + * - FriendlyByteBuf的读写方法保持一致 + */ +public class SetWirelessFrequencyC2SPacket implements CustomPacketPayload { + + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "set_wireless_frequency")); + + /** + * StreamCodec用于序列化和反序列化数据包 + * 第一个函数:编码(写入) + * 第二个函数:解码(读取) + */ + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buf, pkt) -> { + buf.writeBlockPos(pkt.pos); + buf.writeLong(pkt.frequency); + }, + buf -> new SetWirelessFrequencyC2SPacket( + buf.readBlockPos(), + buf.readLong() + ) + ); + + private final BlockPos pos; + private final long frequency; + + public SetWirelessFrequencyC2SPacket(BlockPos pos, long frequency) { + this.pos = pos; + this.frequency = frequency; + } + + @Override + public Type type() { + return TYPE; + } + + /** + * 服务端处理逻辑 + * + * API变化说明: + * - ctx.enqueueWork确保线程安全(在主线程执行) + * - IPayloadContext替代了Forge的NetworkEvent.Context + */ + public static void handle(final SetWirelessFrequencyC2SPacket msg, final IPayloadContext ctx) { + ctx.enqueueWork(() -> { + if (!(ctx.player() instanceof ServerPlayer player)) { + return; + } + + // 验证玩家是否在附近(防止作弊) + if (player.distanceToSqr(msg.pos.getX() + 0.5, msg.pos.getY() + 0.5, msg.pos.getZ() + 0.5) > 64.0) { + ExtendedAEPlus.LOGGER.warn("Player {} tried to set frequency from too far away", player.getName().getString()); + return; + } + + // 获取方块实体 + BlockEntity be = player.level().getBlockEntity(msg.pos); + if (!(be instanceof WirelessTransceiverBlockEntity transceiver)) { + ExtendedAEPlus.LOGGER.warn("Invalid block entity at {} for frequency setting", msg.pos); + return; + } + + // 使用强制设置方法,忽略锁定状态 + // 扳手GUI调整频率时应该能够绕过锁定限制 + transceiver.setFrequencyForced(msg.frequency); + ExtendedAEPlus.LOGGER.debug("Set transceiver frequency at {} to {} (forced)", msg.pos, msg.frequency); + }); + } +} + diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 160fea8..d063ec9 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -118,6 +118,13 @@ "extendedae_plus.tooltip.master_mode": "Mode: %s", "extendedae_plus.tooltip.locked": "Status: %s", + "gui.extendedae_plus.frequency_input.title": "Set Frequency", + "gui.extendedae_plus.frequency_input.field": "Frequency", + "gui.extendedae_plus.frequency_input.confirm": "Confirm", + "gui.extendedae_plus.frequency_input.cancel": "Cancel", + "extendedae_plus.wireless.locked": "Transceiver Locked", + "extendedae_plus.wireless.unlocked": "Transceiver Unlocked", + "group.pattern_provider.name": "Pattern Provider", "group.storage.name": "Storage Bus", "group.entity_ticker.name": "Entity Ticker" diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 840d62c..2dce88d 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -118,6 +118,13 @@ "extendedae_plus.tooltip.master_mode": "模式: %s", "extendedae_plus.tooltip.locked": "状态: %s", + "gui.extendedae_plus.frequency_input.title": "设置频率", + "gui.extendedae_plus.frequency_input.field": "频率", + "gui.extendedae_plus.frequency_input.confirm": "确认", + "gui.extendedae_plus.frequency_input.cancel": "取消", + "extendedae_plus.wireless.locked": "已锁定收发器", + "extendedae_plus.wireless.unlocked": "已解锁收发器", + "group.pattern_provider.name": "样板供应器", "group.storage.name": "存储总线", "group.entity_ticker.name": "实体加速器"