添加扳手调整无线收发器频率

This commit is contained in:
GaLicn 2025-10-05 14:04:56 +08:00
parent 2732a9aee1
commit 0622767f10
7 changed files with 370 additions and 2 deletions

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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<SetWirelessFrequencyC2SPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "set_wireless_frequency"));
/**
* StreamCodec用于序列化和反序列化数据包
* 第一个函数编码写入
* 第二个函数解码读取
*/
public static final StreamCodec<FriendlyByteBuf, SetWirelessFrequencyC2SPacket> 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<? extends CustomPacketPayload> 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);
});
}
}

View File

@ -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"

View File

@ -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": "实体加速器"