UI框架
This commit is contained in:
parent
29d07b1f90
commit
0b4443773d
75
doc/labeled_wireless_transceiver_design.md
Normal file
75
doc/labeled_wireless_transceiver_design.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Labeled Wireless Transceiver 设计方案(标签无线收发器)
|
||||
|
||||
## 目标与命名
|
||||
- 新方块/物品/BE:Labeled Wireless Transceiver(标签无线收发器),注册名 `labeled_wireless_transceiver`。
|
||||
- 功能与旧收发器一致,但增加 UI,使用字符串标签管理频道;底层仍用 long 频率。
|
||||
- 旧收发器保留原行为,频段互不干扰。
|
||||
|
||||
## 交互与 UI(无主从,统一标签界面,虚拟节点中心)
|
||||
- 方块交互:**取消全部徒手/道具交互,只保留右键打开 UI**。
|
||||
- 单一界面,核心元素:
|
||||
1) 全局标签-频道列表(从服务端获取 LabelNetworkRegistry 映射),展示标签名、频道号、在线端点数。
|
||||
2) 当前收发器的标签与频道号(只读频道号,标签可编辑/选择)。
|
||||
3) 连接状态:已连接、未连接、离线/超距(可用状态徽标)。
|
||||
4) 操作按钮:
|
||||
- “新建标签”:输入合法标签,分配频道并创建/获取对应虚拟节点,当前收发器加入该网络。
|
||||
- “删除标签”:删除当前标签映射(当网络端点数为 0 时销毁虚拟节点并回收频道),当前收发器清空标签并断开。
|
||||
- “设为当前频道”:将当前收发器切换到列表选中的标签网络(连接到该标签的虚拟节点)。
|
||||
- “刷新列表”:从服务端重新获取映射与统计。
|
||||
5) 搜索/过滤框(可选):按标签关键字过滤列表。
|
||||
- UI 流程(建议):
|
||||
- 打开 UI → 获取全局映射列表 + 当前端点的标签/频道 + 连接状态 → 填充列表和回显。
|
||||
- 应用/切换标签:发送 C2S 包(BlockPos + label + 操作类型),服务端分配/查询频道号、创建或获取虚拟节点并写回 BE。
|
||||
- 删除:发送删除操作;服务端移除映射(若网络无端点则销毁虚拟节点,回收频道),将该 BE 频率清零;列表与回显刷新。
|
||||
|
||||
## 标签网络注册中心(虚拟节点中心)
|
||||
- 新建 `LabelNetworkRegistry`(SavedData/服务端单例)管理标签网络:
|
||||
- Key:`(label标准化, 维度或 null 取决于跨维配置, owner/team UUID)`
|
||||
- Value:`LabelNetwork`:`Set<EndpointRef>` 端点集合、`VirtualNodeRef virtualNode`(虚拟 AE2 节点句柄)、`long channel`(专用频道号)、在线统计。
|
||||
- 虚拟节点:
|
||||
- 创建:新建/首用标签时,使用 `ManagedGridNode`,`setInWorldNode(false)`,`create(level, null)` 创建非 in-world 节点;`setIdlePowerUsage(0)`(可配置)并设置 visual representation。
|
||||
- 维度选择:若跨维开启,统一用主世界;否则按所在维度创建对应虚拟节点。
|
||||
- 持久化:SavedData 记录虚拟节点需要的重建信息(labelKey、channel、owner/team);重启时重建虚拟节点并复用频道。
|
||||
- 连接拓扑:
|
||||
- 所有收发器端点直接连接到该标签的虚拟节点(单中心,避免选举和 n² 连接)。
|
||||
- 当标签网络端点数为 0 时,销毁虚拟节点并回收频道号。
|
||||
- 频道号分配:
|
||||
- 预留专用频率区间:从 `1_000_000` 起向上分配,防止与旧版收发器冲突。
|
||||
- 频道号存放在 LabelNetwork 中,端点读取后设置自己的 `frequency` 字段即可,无需改底层 AE2 API。
|
||||
- API 建议:
|
||||
- `register(endpoint, label, owner, level)`:加入网络,若无虚拟节点则创建并分配频道。
|
||||
- `unregister(endpoint)`:移除端点,若网络端点为 0 则销毁虚拟节点并回收频道。
|
||||
- `setLabel(endpoint, newLabel)`:先注销再按新标签注册。
|
||||
- `listNetworks(owner, dim/null)`:供 UI 拉取“标签-频道-在线数”。
|
||||
- `getNetwork(labelKey)`:UI 获取当前标签网络的在线端/状态。
|
||||
- 持久化:SavedData 持久化标签→频道号→端点集合(弱引用/位置)及虚拟节点重建所需信息;主线程访问,定期清理无效引用。
|
||||
- 校验:字符集 `[A-Za-z0-9_-]`,长度 ≤ 32(或 64),标准化 trim + lower,空串无效。
|
||||
|
||||
## BE 与逻辑复用
|
||||
- BE 保留 `long frequency` 字段与节点/连接逻辑,不分主从;连接目标是标签对应的虚拟节点。
|
||||
- 新增字段:`String labelForDisplay`(UI 回显);`long frequency` 由标签网络分配。
|
||||
- NBT:继续存 `frequency`(long)和 `label` 字符串;加载后通过 registry 获取频道号并连向虚拟节点。
|
||||
- 连接实现:可复用 `WirelessSlaveLink` 的“连接到指定节点”能力,去掉主端假设;或实现单一 `LabelLink`,始终尝试连接虚拟节点(失败则重试/断开)。
|
||||
|
||||
## 网络与数据包
|
||||
- 新包:`LabelNetworkActionC2SPacket`(BlockPos + label + 操作类型[新建/删除/切换])。
|
||||
- 服务端处理:
|
||||
1) 校验 label → 调用 `LabelNetworkRegistry` 分配/切换/删除;必要时创建/销毁虚拟节点。
|
||||
2) 写入 BE:更新 `frequency`、`labelForDisplay`;指向虚拟节点并重连。
|
||||
3) 反馈消息包含标签、频道号、在线数/连接状态。
|
||||
|
||||
## 方块与资源
|
||||
- 方块模型/贴图/语言键需新增(`block.extendedae_plus.labeled_wireless_transceiver` 等)。
|
||||
- 配方独立(避免与旧版冲突)。
|
||||
- BlockState:可复用旧收发器的 STATE 显示逻辑。
|
||||
|
||||
## 配置项(可选)
|
||||
- 映射区间起始/上限。
|
||||
- 标签字符集/长度限制。
|
||||
- 跨维度共享开关(与现有无线配置保持一致)。
|
||||
|
||||
## 风险与注意点
|
||||
- 并发分配:务必在服务端单点分配并二次检查注册中心占用,防止重复。
|
||||
- 同名稳定:同 owner/team 的同名需返回同一频道号,否则可能导致已有网络断开。
|
||||
- 预留区间耗尽:需提示玩家清理无用标签或回退使用旧版数值收发器。
|
||||
- 交互收缩:已移除所有徒手 +/- 频率操作,玩家需通过 UI 设置标签。
|
||||
|
|
@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* 标签无线网络注册中心(SavedData)。
|
||||
|
|
@ -47,6 +48,8 @@ public class LabelNetworkRegistry extends SavedData {
|
|||
return level.getDataStorage().computeIfAbsent(LabelNetworkRegistry::load, LabelNetworkRegistry::new, SAVE_ID);
|
||||
}
|
||||
|
||||
public record LabelNetworkSnapshot(String label, long channel) {}
|
||||
|
||||
public static LabelNetworkRegistry get(ServerLevel level) {
|
||||
return get(level.getServer());
|
||||
}
|
||||
|
|
@ -131,6 +134,23 @@ public class LabelNetworkRegistry extends SavedData {
|
|||
return networks.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前玩家所属网络列表(按标签排序)。
|
||||
*/
|
||||
public synchronized List<LabelNetworkSnapshot> listNetworks(ServerLevel level, @Nullable UUID placerId) {
|
||||
UUID owner = placerId == null ? WirelessMasterRegistry.PUBLIC_NETWORK_UUID : WirelessTeamUtil.getNetworkOwnerUUID(level, placerId);
|
||||
ResourceKey<Level> dimKey = ModConfig.INSTANCE.wirelessCrossDimEnable ? null : level.dimension();
|
||||
List<LabelNetworkSnapshot> list = new ArrayList<>();
|
||||
for (Map.Entry<Key, LabelNetwork> entry : networks.entrySet()) {
|
||||
Key key = entry.getKey();
|
||||
if (!Objects.equals(key.owner(), owner)) continue;
|
||||
if (!Objects.equals(key.dim(), dimKey)) continue;
|
||||
list.add(new LabelNetworkSnapshot(key.label(), entry.getValue().channel()));
|
||||
}
|
||||
list.sort(Comparator.comparing(LabelNetworkSnapshot::label));
|
||||
return list;
|
||||
}
|
||||
|
||||
/* 序列化 */
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
|||
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
|
||||
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
|
||||
import com.extendedae_plus.client.screen.GlobalProviderModesScreen;
|
||||
import com.extendedae_plus.client.screen.LabeledWirelessTransceiverScreen;
|
||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||
import com.extendedae_plus.hooks.BuiltInModelHooks;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
|
|
@ -60,6 +61,7 @@ public final class ClientRegistrar {
|
|||
*/
|
||||
public static void registerMenuScreens() {
|
||||
MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new);
|
||||
MenuScreens.register(ModMenuTypes.LABELED_WIRELESS_TRANSCEIVER.get(), LabeledWirelessTransceiverScreen::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
package com.extendedae_plus.client.screen;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
|
||||
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
|
||||
import com.extendedae_plus.network.LabelNetworkActionC2SPacket;
|
||||
import com.extendedae_plus.network.LabelNetworkListC2SPacket;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.components.ImageButton;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 标签无线收发器屏幕(UI 占位,等待按钮布局)。
|
||||
* 纹理:textures/gui/lable_wireless_transceiver_gui.png,尺寸 194x156。
|
||||
*/
|
||||
public class LabeledWirelessTransceiverScreen extends AbstractContainerScreen<LabeledWirelessTransceiverMenu> {
|
||||
private static final ResourceLocation TEX = ExtendedAEPlus.id("textures/gui/lable_wireless_transceiver_gui.png");
|
||||
private static final int BTN_U = 197;
|
||||
private static final int BTN_V = 54;
|
||||
private static final int BTN_W = 28;
|
||||
private static final int BTN_H = 16;
|
||||
private static final int TEX_W = 256;
|
||||
private static final int TEX_H = 256;
|
||||
|
||||
private static final int LIST_X = 8;
|
||||
private static final int LIST_Y = 21;
|
||||
private static final int LIST_W = 100;
|
||||
private static final int LIST_H = 121; // 141-21+1
|
||||
private static final int ROW_H = 12;
|
||||
private static final int VISIBLE_ROWS = LIST_H / ROW_H; // 10
|
||||
private static final int SCROLL_X = 111;
|
||||
private static final int SCROLL_Y = 21;
|
||||
private static final int SCROLL_W = 6;
|
||||
private static final int SCROLL_H = LIST_H;
|
||||
|
||||
private EditBox searchBox;
|
||||
private ImageButton newBtn;
|
||||
private ImageButton deleteBtn;
|
||||
private ImageButton setBtn;
|
||||
private ImageButton disconnectBtn;
|
||||
|
||||
private final BlockPos bePos;
|
||||
private final List<LabelEntry> entries = new ArrayList<>();
|
||||
private final List<LabelEntry> filtered = new ArrayList<>();
|
||||
private int scrollOffset = 0;
|
||||
private int selectedIndex = -1;
|
||||
private String currentLabel = "";
|
||||
private long currentChannel = 0L;
|
||||
|
||||
public LabeledWirelessTransceiverScreen(LabeledWirelessTransceiverMenu menu, Inventory inv, Component title) {
|
||||
super(menu, inv, title);
|
||||
this.imageWidth = 194;
|
||||
this.imageHeight = 156;
|
||||
this.inventoryLabelY = this.imageHeight; // 不显示玩家物品栏标签
|
||||
this.bePos = menu.getBlockEntityPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
// 搜索框:起点(78,4) 终点(189,15) => 宽112 高12
|
||||
int sx = this.leftPos + 78;
|
||||
int sy = this.topPos + 4;
|
||||
this.searchBox = new EditBox(this.font, sx, sy, 112, 12, Component.empty());
|
||||
this.searchBox.setBordered(false);
|
||||
this.searchBox.setMaxLength(64);
|
||||
this.searchBox.setVisible(true);
|
||||
this.searchBox.setFocused(false);
|
||||
this.searchBox.setResponder(s -> {
|
||||
applyFilter();
|
||||
});
|
||||
this.addRenderableWidget(this.searchBox);
|
||||
|
||||
int startX = this.leftPos + 124;
|
||||
int startY = this.topPos + 91;
|
||||
int hGap = 8;
|
||||
int vGap = 10;
|
||||
int secondColX = startX + BTN_W + hGap;
|
||||
int secondRowY = startY + BTN_H + vGap;
|
||||
|
||||
this.newBtn = new ImageButton(startX, startY, BTN_W, BTN_H, BTN_U, BTN_V, 0, TEX, TEX_W, TEX_H,
|
||||
b -> sendSet(searchBox.getValue()), Component.translatable("gui.extendedae_plus.labeled_wireless.button.new"));
|
||||
this.deleteBtn = new ImageButton(secondColX, startY, BTN_W, BTN_H, BTN_U, BTN_V, 0, TEX, TEX_W, TEX_H,
|
||||
b -> sendDelete(), Component.translatable("gui.extendedae_plus.labeled_wireless.button.delete"));
|
||||
this.setBtn = new ImageButton(startX, secondRowY, BTN_W, BTN_H, BTN_U, BTN_V, 0, TEX, TEX_W, TEX_H,
|
||||
b -> sendSet(getSelectedLabel()), Component.translatable("gui.extendedae_plus.labeled_wireless.button.set"));
|
||||
this.disconnectBtn = new ImageButton(secondColX, secondRowY, BTN_W, BTN_H, BTN_U, BTN_V, 0, TEX, TEX_W, TEX_H,
|
||||
b -> sendDisconnect(), Component.translatable("gui.extendedae_plus.labeled_wireless.button.refresh"));
|
||||
|
||||
this.addRenderableWidget(this.newBtn);
|
||||
this.addRenderableWidget(this.deleteBtn);
|
||||
this.addRenderableWidget(this.setBtn);
|
||||
this.addRenderableWidget(this.disconnectBtn);
|
||||
|
||||
requestList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) {
|
||||
this.renderBackground(gfx);
|
||||
super.render(gfx, mouseX, mouseY, partialTicks);
|
||||
drawAllButtonText(gfx);
|
||||
this.renderTooltip(gfx, mouseX, mouseY);
|
||||
if (this.searchBox != null) {
|
||||
this.searchBox.render(gfx, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderLabels(GuiGraphics gfx, int mouseX, int mouseY) {
|
||||
// 左上角标题
|
||||
gfx.drawString(this.font, this.title, 8, 8, 0x404040, false);
|
||||
// 右侧信息区标题
|
||||
gfx.drawString(this.font, Component.translatable("gui.extendedae_plus.labeled_wireless.info"), 124, 24, 0x404040, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics gfx, float partialTicks, int mouseX, int mouseY) {
|
||||
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
|
||||
gfx.blit(TEX, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight);
|
||||
|
||||
// 占位绘制:列表和信息区内的内容框线
|
||||
// 标签列表区域
|
||||
gfx.fill(this.leftPos + 8, this.topPos + 21, this.leftPos + 107 + 1, this.topPos + 141 + 1, 0x20FFFFFF);
|
||||
// 滚动条区域
|
||||
gfx.fill(this.leftPos + 111, this.topPos + 21, this.leftPos + 116 + 1, this.topPos + 141 + 1, 0x20000000);
|
||||
// 当前收发器信息区域
|
||||
gfx.fill(this.leftPos + 121, this.topPos + 21, this.leftPos + 189 + 1, this.topPos + 76 + 1, 0x10FFFFFF);
|
||||
|
||||
renderList(gfx);
|
||||
renderScrollBar(gfx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (this.searchBox != null && this.searchBox.mouseClicked(mouseX, mouseY, button)) {
|
||||
setFocused(this.searchBox);
|
||||
return true;
|
||||
}
|
||||
if (isMouseInList(mouseX, mouseY)) {
|
||||
int localY = (int) mouseY - (this.topPos + LIST_Y);
|
||||
int row = localY / ROW_H;
|
||||
int idx = scrollOffset + row;
|
||||
if (idx >= 0 && idx < filtered.size()) {
|
||||
selectedIndex = idx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isMouseInScrollbar(mouseX, mouseY)) {
|
||||
updateScrollByMouse((int) mouseY);
|
||||
return true;
|
||||
}
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||
if (this.searchBox != null && this.searchBox.keyPressed(keyCode, scanCode, modifiers)) {
|
||||
return true;
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
|
||||
if (isMouseInList(mouseX, mouseY) || isMouseInScrollbar(mouseX, mouseY)) {
|
||||
int maxOffset = Math.max(0, filtered.size() - VISIBLE_ROWS);
|
||||
scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset - (int) Math.signum(delta)));
|
||||
return true;
|
||||
}
|
||||
return super.mouseScrolled(mouseX, mouseY, delta);
|
||||
}
|
||||
|
||||
private void renderList(GuiGraphics gfx) {
|
||||
int baseX = this.leftPos + LIST_X;
|
||||
int baseY = this.topPos + LIST_Y;
|
||||
for (int row = 0; row < VISIBLE_ROWS; row++) {
|
||||
int idx = scrollOffset + row;
|
||||
if (idx >= filtered.size()) break;
|
||||
int y = baseY + row * ROW_H;
|
||||
if (idx == selectedIndex) {
|
||||
gfx.fill(baseX, y, baseX + LIST_W, y + ROW_H, 0x40FFFFFF);
|
||||
}
|
||||
LabelEntry e = filtered.get(idx);
|
||||
String text = this.font.plainSubstrByWidth(e.label(), LIST_W - 4);
|
||||
gfx.drawString(this.font, text, baseX + 2, y + 2, 0x404040, false);
|
||||
}
|
||||
|
||||
// 信息显示
|
||||
int infoX = this.leftPos + 124;
|
||||
int infoY = this.topPos + 36;
|
||||
String labelLine = Component.translatable("gui.extendedae_plus.labeled_wireless.current_label").getString() + ": " + (currentLabel == null || currentLabel.isEmpty() ? "-" : currentLabel);
|
||||
String channelLine = Component.translatable("gui.extendedae_plus.labeled_wireless.current_channel").getString() + ": " + currentChannel;
|
||||
gfx.drawString(this.font, labelLine, infoX, infoY, 0x404040, false);
|
||||
gfx.drawString(this.font, channelLine, infoX, infoY + 12, 0x404040, false);
|
||||
}
|
||||
|
||||
private void renderScrollBar(GuiGraphics gfx) {
|
||||
int total = filtered.size();
|
||||
if (total <= VISIBLE_ROWS) {
|
||||
// 画静态条
|
||||
gfx.fill(this.leftPos + SCROLL_X, this.topPos + SCROLL_Y, this.leftPos + SCROLL_X + SCROLL_W, this.topPos + SCROLL_Y + SCROLL_H, 0x20000000);
|
||||
return;
|
||||
}
|
||||
int maxOffset = total - VISIBLE_ROWS;
|
||||
int trackX1 = this.leftPos + SCROLL_X;
|
||||
int trackY1 = this.topPos + SCROLL_Y;
|
||||
int trackY2 = trackY1 + SCROLL_H;
|
||||
gfx.fill(trackX1, trackY1, trackX1 + SCROLL_W, trackY2, 0x20000000);
|
||||
int knobH = Math.max(10, (int) ((double) VISIBLE_ROWS / total * SCROLL_H));
|
||||
int knobY = trackY1 + (int) ((SCROLL_H - knobH) * (scrollOffset / (double) maxOffset));
|
||||
gfx.fill(trackX1, knobY, trackX1 + SCROLL_W, knobY + knobH, 0x80FFFFFF);
|
||||
}
|
||||
|
||||
private boolean isMouseInList(double mouseX, double mouseY) {
|
||||
return mouseX >= this.leftPos + LIST_X && mouseX < this.leftPos + LIST_X + LIST_W
|
||||
&& mouseY >= this.topPos + LIST_Y && mouseY < this.topPos + LIST_Y + LIST_H;
|
||||
}
|
||||
|
||||
private boolean isMouseInScrollbar(double mouseX, double mouseY) {
|
||||
return mouseX >= this.leftPos + SCROLL_X && mouseX < this.leftPos + SCROLL_X + SCROLL_W
|
||||
&& mouseY >= this.topPos + SCROLL_Y && mouseY < this.topPos + SCROLL_Y + SCROLL_H;
|
||||
}
|
||||
|
||||
private void updateScrollByMouse(int mouseY) {
|
||||
int total = filtered.size();
|
||||
if (total <= VISIBLE_ROWS) return;
|
||||
int maxOffset = total - VISIBLE_ROWS;
|
||||
int relativeY = mouseY - (this.topPos + SCROLL_Y);
|
||||
relativeY = Math.max(0, Math.min(SCROLL_H, relativeY));
|
||||
int knobH = Math.max(10, (int) ((double) VISIBLE_ROWS / total * SCROLL_H));
|
||||
double ratio = (relativeY - knobH / 2.0) / (double) (SCROLL_H - knobH);
|
||||
ratio = Math.max(0.0, Math.min(1.0, ratio));
|
||||
scrollOffset = (int) Math.round(ratio * maxOffset);
|
||||
}
|
||||
|
||||
private void applyFilter() {
|
||||
String q = searchBox.getValue() == null ? "" : searchBox.getValue().trim().toLowerCase();
|
||||
filtered.clear();
|
||||
if (q.isEmpty()) {
|
||||
filtered.addAll(entries);
|
||||
} else {
|
||||
for (LabelEntry e : entries) {
|
||||
if (e.label().toLowerCase().contains(q)) {
|
||||
filtered.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
scrollOffset = 0;
|
||||
selectedIndex = filtered.isEmpty() ? -1 : Math.min(selectedIndex, filtered.size() - 1);
|
||||
}
|
||||
|
||||
private void requestList() {
|
||||
ModNetwork.CHANNEL.sendToServer(new LabelNetworkListC2SPacket(bePos));
|
||||
}
|
||||
|
||||
private void sendSet(String label) {
|
||||
if (label == null) label = "";
|
||||
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, label, LabelNetworkActionC2SPacket.Action.SET));
|
||||
requestList();
|
||||
}
|
||||
|
||||
private void sendDelete() {
|
||||
String label = getSelectedLabel();
|
||||
if (label == null || label.isEmpty()) {
|
||||
label = searchBox.getValue();
|
||||
}
|
||||
if (label == null) label = "";
|
||||
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, label, LabelNetworkActionC2SPacket.Action.DELETE));
|
||||
requestList();
|
||||
}
|
||||
|
||||
private void sendDisconnect() {
|
||||
ModNetwork.CHANNEL.sendToServer(new LabelNetworkActionC2SPacket(bePos, "", LabelNetworkActionC2SPacket.Action.DISCONNECT));
|
||||
requestList();
|
||||
}
|
||||
|
||||
private String getSelectedLabel() {
|
||||
if (selectedIndex >= 0 && selectedIndex < filtered.size()) {
|
||||
return filtered.get(selectedIndex).label();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void updateList(List<LabelNetworkRegistry.LabelNetworkSnapshot> list, String currentLabel, long currentChannel) {
|
||||
this.entries.clear();
|
||||
for (LabelNetworkRegistry.LabelNetworkSnapshot s : list) {
|
||||
this.entries.add(new LabelEntry(s.label(), s.channel()));
|
||||
}
|
||||
this.currentLabel = currentLabel == null ? "" : currentLabel;
|
||||
this.currentChannel = currentChannel;
|
||||
applyFilter();
|
||||
}
|
||||
|
||||
public boolean isFor(BlockPos pos) {
|
||||
return this.bePos.equals(pos);
|
||||
}
|
||||
|
||||
private record LabelEntry(String label, long channel) {}
|
||||
|
||||
private void drawAllButtonText(GuiGraphics gfx) {
|
||||
// 按钮文本(24px 内居中,避免溢出)。放在 super.render 之后,确保绘制在按钮纹理之上。
|
||||
int startX = this.leftPos + 124;
|
||||
int startY = this.topPos + 91;
|
||||
int hGap = 8;
|
||||
int vGap = 10;
|
||||
int secondColX = startX + BTN_W + hGap;
|
||||
int secondRowY = startY + BTN_H + vGap;
|
||||
|
||||
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.new"), startX, startY);
|
||||
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.delete"), secondColX, startY);
|
||||
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.set"), startX, secondRowY);
|
||||
drawButtonText(gfx, Component.translatable("gui.extendedae_plus.labeled_wireless.button.refresh"), secondColX, secondRowY);
|
||||
}
|
||||
|
||||
private void drawButtonText(GuiGraphics gfx, Component text, int x, int y) {
|
||||
String s = this.font.plainSubstrByWidth(text.getString(), BTN_W - 4);
|
||||
int tx = x + (BTN_W - this.font.width(s)) / 2;
|
||||
int ty = y + (BTN_H - this.font.lineHeight) / 2 + 1;
|
||||
gfx.drawString(this.font, s, tx, ty, 0xFFFFFF, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
|
|
@ -56,8 +57,15 @@ public class LabeledWirelessTransceiverBlock extends Block implements EntityBloc
|
|||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
// UI 暂未实现,直接吞掉交互,防止旧版徒手调频。
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
if (level.isClientSide) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (be instanceof LabeledWirelessTransceiverBlockEntity te) {
|
||||
NetworkHooks.openScreen((net.minecraft.server.level.ServerPlayer) player, te, buf -> buf.writeBlockPos(pos));
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,12 +13,18 @@ import com.extendedae_plus.ae.wireless.LabelLink;
|
|||
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
|
||||
import com.extendedae_plus.init.ModBlockEntities;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
|
@ -31,7 +37,7 @@ import java.util.UUID;
|
|||
* - 无 UI(占位),仅提供服务端逻辑与状态更新;
|
||||
* - 保留频率字段用于状态显示。
|
||||
*/
|
||||
public class LabeledWirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
|
||||
public class LabeledWirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost, MenuProvider {
|
||||
|
||||
private IManagedGridNode managedNode;
|
||||
|
||||
|
|
@ -87,6 +93,17 @@ public class LabeledWirelessTransceiverBlockEntity extends AEBaseBlockEntity imp
|
|||
return super.isRemoved();
|
||||
}
|
||||
|
||||
/* ===================== 菜单接口 ===================== */
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return Component.translatable("block.extendedae_plus.labeled_wireless_transceiver");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int id, Inventory inv, Player player) {
|
||||
return new LabeledWirelessTransceiverMenu(id, inv, this.worldPosition);
|
||||
}
|
||||
|
||||
/* ===================== 公共方法 ===================== */
|
||||
|
||||
public void setPlacerId(@Nullable UUID placerId, @Nullable String placerName) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public final class ModCreativeTabs {
|
|||
.displayItems((params, output) -> {
|
||||
// 将本模组物品加入创造物品栏
|
||||
output.accept(ModItems.WIRELESS_TRANSCEIVER.get());
|
||||
output.accept(ModItems.LABELED_WIRELESS_TRANSCEIVER.get());
|
||||
output.accept(ModItems.NETWORK_PATTERN_CONTROLLER.get());
|
||||
// 装配矩阵上传核心
|
||||
output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get());
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import appeng.menu.implementations.MenuTypeBuilder;
|
|||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||
import com.extendedae_plus.menu.LabeledWirelessTransceiverMenu;
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraftforge.common.extensions.IForgeMenuType;
|
||||
|
|
@ -22,6 +23,10 @@ public final class ModMenuTypes {
|
|||
MENUS.register("network_pattern_controller",
|
||||
() -> IForgeMenuType.create(NetworkPatternControllerMenu::new));
|
||||
|
||||
public static final RegistryObject<MenuType<LabeledWirelessTransceiverMenu>> LABELED_WIRELESS_TRANSCEIVER =
|
||||
MENUS.register("labeled_wireless_transceiver",
|
||||
() -> IForgeMenuType.create(LabeledWirelessTransceiverMenu::new));
|
||||
|
||||
public static final RegistryObject<MenuType<EntitySpeedTickerMenu>> ENTITY_TICKER_MENU =
|
||||
MENUS.register("entity_speed_ticker",
|
||||
() -> MenuTypeBuilder
|
||||
|
|
|
|||
|
|
@ -156,6 +156,18 @@ public final class ModNetwork {
|
|||
.decoder(LabelNetworkActionC2SPacket::decode)
|
||||
.consumerNetworkThread(LabelNetworkActionC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(LabelNetworkListC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(LabelNetworkListC2SPacket::encode)
|
||||
.decoder(LabelNetworkListC2SPacket::decode)
|
||||
.consumerNetworkThread(LabelNetworkListC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(LabelNetworkListS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(LabelNetworkListS2CPacket::encode)
|
||||
.decoder(LabelNetworkListS2CPacket::decode)
|
||||
.consumerNetworkThread(LabelNetworkListS2CPacket::handle)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static int nextId() { return id++; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.extendedae_plus.menu;
|
||||
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* 标签无线收发器菜单(暂无线槽,仅用于打开客户端界面)。
|
||||
*/
|
||||
public class LabeledWirelessTransceiverMenu extends AbstractContainerMenu {
|
||||
private final BlockPos bePos;
|
||||
|
||||
public LabeledWirelessTransceiverMenu(int id, Inventory inv, BlockPos bePos) {
|
||||
super(ModMenuTypes.LABELED_WIRELESS_TRANSCEIVER.get(), id);
|
||||
this.bePos = bePos;
|
||||
}
|
||||
|
||||
public LabeledWirelessTransceiverMenu(int id, Inventory inv, FriendlyByteBuf buf) {
|
||||
this(id, inv, buf.readBlockPos());
|
||||
}
|
||||
|
||||
public BlockPos getBlockEntityPos() {
|
||||
return bePos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return player.level() != null
|
||||
&& player.level().getBlockEntity(bePos) != null
|
||||
&& player.distanceToSqr(bePos.getX() + 0.5, bePos.getY() + 0.5, bePos.getZ() + 0.5) <= 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import java.util.function.Supplier;
|
|||
*/
|
||||
public class LabelNetworkActionC2SPacket {
|
||||
public enum Action {
|
||||
SET, DELETE, REFRESH
|
||||
SET, DELETE, DISCONNECT
|
||||
}
|
||||
|
||||
private final BlockPos pos;
|
||||
|
|
@ -52,8 +52,7 @@ public class LabelNetworkActionC2SPacket {
|
|||
|
||||
switch (packet.action) {
|
||||
case SET -> te.applyLabel(packet.label);
|
||||
case DELETE -> te.clearLabel();
|
||||
case REFRESH -> te.refreshLabel();
|
||||
case DELETE, DISCONNECT -> te.clearLabel();
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
|
||||
import com.extendedae_plus.content.wireless.LabeledWirelessTransceiverBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 请求标签网络列表(客户端 -> 服务端)。
|
||||
*/
|
||||
public class LabelNetworkListC2SPacket {
|
||||
private final BlockPos pos;
|
||||
|
||||
public LabelNetworkListC2SPacket(BlockPos pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public static void encode(LabelNetworkListC2SPacket pkt, FriendlyByteBuf buf) {
|
||||
buf.writeBlockPos(pkt.pos);
|
||||
}
|
||||
|
||||
public static LabelNetworkListC2SPacket decode(FriendlyByteBuf buf) {
|
||||
return new LabelNetworkListC2SPacket(buf.readBlockPos());
|
||||
}
|
||||
|
||||
public static void handle(LabelNetworkListC2SPacket pkt, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.get().getSender();
|
||||
if (player == null) return;
|
||||
var level = player.serverLevel();
|
||||
if (!level.hasChunkAt(pkt.pos)) return;
|
||||
var be = level.getBlockEntity(pkt.pos);
|
||||
if (!(be instanceof LabeledWirelessTransceiverBlockEntity te)) return;
|
||||
|
||||
var list = LabelNetworkRegistry.get(level).listNetworks(level, te.getPlacerId());
|
||||
String currentLabel = te.getLabelForDisplay();
|
||||
long currentChannel = te.getFrequency();
|
||||
LabelNetworkListS2CPacket rsp = new LabelNetworkListS2CPacket(pkt.pos, list, currentLabel, currentChannel);
|
||||
com.extendedae_plus.init.ModNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), rsp);
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import com.extendedae_plus.ae.wireless.LabelNetworkRegistry;
|
||||
import com.extendedae_plus.client.screen.LabeledWirelessTransceiverScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 标签网络列表下发(服务端 -> 客户端)。
|
||||
*/
|
||||
public class LabelNetworkListS2CPacket {
|
||||
private final BlockPos pos;
|
||||
private final List<LabelNetworkRegistry.LabelNetworkSnapshot> list;
|
||||
private final String currentLabel;
|
||||
private final long currentChannel;
|
||||
|
||||
public LabelNetworkListS2CPacket(BlockPos pos, List<LabelNetworkRegistry.LabelNetworkSnapshot> list, String currentLabel, long currentChannel) {
|
||||
this.pos = pos;
|
||||
this.list = list;
|
||||
this.currentLabel = currentLabel;
|
||||
this.currentChannel = currentChannel;
|
||||
}
|
||||
|
||||
public static void encode(LabelNetworkListS2CPacket pkt, FriendlyByteBuf buf) {
|
||||
buf.writeBlockPos(pkt.pos);
|
||||
buf.writeUtf(pkt.currentLabel == null ? "" : pkt.currentLabel, 128);
|
||||
buf.writeLong(pkt.currentChannel);
|
||||
buf.writeVarInt(pkt.list.size());
|
||||
for (LabelNetworkRegistry.LabelNetworkSnapshot s : pkt.list) {
|
||||
buf.writeUtf(s.label(), 128);
|
||||
buf.writeLong(s.channel());
|
||||
}
|
||||
}
|
||||
|
||||
public static LabelNetworkListS2CPacket decode(FriendlyByteBuf buf) {
|
||||
BlockPos pos = buf.readBlockPos();
|
||||
String curLabel = buf.readUtf(128);
|
||||
long curChannel = buf.readLong();
|
||||
int size = buf.readVarInt();
|
||||
List<LabelNetworkRegistry.LabelNetworkSnapshot> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String label = buf.readUtf(128);
|
||||
long channel = buf.readLong();
|
||||
list.add(new LabelNetworkRegistry.LabelNetworkSnapshot(label, channel));
|
||||
}
|
||||
return new LabelNetworkListS2CPacket(pos, list, curLabel, curChannel);
|
||||
}
|
||||
|
||||
public static void handle(LabelNetworkListS2CPacket pkt, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> handleClient(pkt));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private static void handleClient(LabelNetworkListS2CPacket pkt) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.screen instanceof LabeledWirelessTransceiverScreen screen && screen.isFor(pkt.pos)) {
|
||||
screen.updateList(pkt.list, pkt.currentLabel, pkt.currentChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
"item.extendedae_plus.spatial_core": "Spatial Core",
|
||||
"item.extendedae_plus.oblivion_singularity": "Oblivion Singularity",
|
||||
"item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence",
|
||||
"item.extendedae_plus.labeled_wireless_transceiver": "Labeled Wireless Transceiver",
|
||||
"item.extendedae_plus.basic_core.storage.1": "Basic Core·Digitized",
|
||||
"item.extendedae_plus.basic_core.storage.2": "Basic Core·Arrayed",
|
||||
"item.extendedae_plus.basic_core.storage.3": "Basic Core·Matricized",
|
||||
|
|
@ -59,6 +60,7 @@
|
|||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "Only crafting patterns supported, processing patterns ignored",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "No formed Assembler Matrix found in network",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "Assembler Matrix pattern storage full or cannot insert",
|
||||
"block.extendedae_plus.labeled_wireless_transceiver": "Labeled Wireless Transceiver",
|
||||
"block.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
|
||||
"block.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
|
||||
"block.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
|
||||
|
|
@ -96,6 +98,14 @@
|
|||
"extendedae_plus.gui.redstone_control.enabled": "Control acceleration with redstone signal",
|
||||
"extendedae_plus.gui.redstone_control.disabled": "Ignore redstone signals",
|
||||
|
||||
"gui.extendedae_plus.labeled_wireless.info": "Transceiver Info",
|
||||
"gui.extendedae_plus.labeled_wireless.button.new": "New Label",
|
||||
"gui.extendedae_plus.labeled_wireless.button.delete": "Delete",
|
||||
"gui.extendedae_plus.labeled_wireless.button.set": "Set Current",
|
||||
"gui.extendedae_plus.labeled_wireless.button.refresh": "Disconnect",
|
||||
"gui.extendedae_plus.labeled_wireless.current_label": "Current Label",
|
||||
"gui.extendedae_plus.labeled_wireless.current_channel": "Current Channel",
|
||||
|
||||
"extendedae_plus.screen.reload_mapping": "Reload Mapping",
|
||||
"extendedae_plus.screen.reload_mapping_success": "Overloading mapping successful",
|
||||
"extendedae_plus.screen.reload_mapping_fail": "Overloading mapping failed: %s",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
"block.extendedae_plus.assembler_matrix_speed_plus": "超级装配矩阵速度核心",
|
||||
"block.extendedae_plus.assembler_matrix_crafter_plus": "超级装配矩阵合成核心",
|
||||
"block.extendedae_plus.assembler_matrix_pattern_plus": "超级装配矩阵样板核心",
|
||||
"block.extendedae_plus.labeled_wireless_transceiver": "标签无线收发器",
|
||||
|
||||
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
|
||||
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
|
||||
|
|
@ -96,6 +97,14 @@
|
|||
"extendedae_plus.gui.redstone_control.enabled": "使用红石信号控制加速",
|
||||
"extendedae_plus.gui.redstone_control.disabled": "忽略红石信号",
|
||||
|
||||
"gui.extendedae_plus.labeled_wireless.info": "收发器信息",
|
||||
"gui.extendedae_plus.labeled_wireless.button.new": "新建标签",
|
||||
"gui.extendedae_plus.labeled_wireless.button.delete": "删除",
|
||||
"gui.extendedae_plus.labeled_wireless.button.set": "设为当前",
|
||||
"gui.extendedae_plus.labeled_wireless.button.refresh": "断开连接",
|
||||
"gui.extendedae_plus.labeled_wireless.current_label": "当前标签",
|
||||
"gui.extendedae_plus.labeled_wireless.current_channel": "当前频道",
|
||||
|
||||
"extendedae_plus.screen.reload_mapping": "重载映射",
|
||||
"extendedae_plus.screen.reload_mapping_success": "重载映射成功",
|
||||
"extendedae_plus.screen.reload_mapping_fail": "重载映射失败: %s",
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in New Issue
Block a user