diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java index 2cd974c..29ff855 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java @@ -20,7 +20,6 @@ import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import com.glodblock.github.extendedae.network.EPPNetworkHandler; import com.glodblock.github.glodium.network.packet.CGenericPacket; import com.google.common.collect.HashMultimap; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; @@ -28,7 +27,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; @@ -44,7 +42,10 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import static com.extendedae_plus.util.GlobalSendMessage.sendPlayerMessage; + @Pseudo +@SuppressWarnings({"AddedMixinMembersNamePattern"}) @Mixin(value = GuiExPatternTerminal.class) public abstract class GuiExPatternTerminalMixin extends AEBaseScreen { @Shadow(remap = false) @Final private static int GUI_PADDING_X; @@ -52,14 +53,12 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen @Shadow(remap = false) @Final private static int GUI_HEADER_HEIGHT; @Shadow(remap = false) @Final private static int ROW_HEIGHT; @Shadow(remap = false) @Final private static int TEXT_MAX_WIDTH; - @Unique private final Map eap$openUIButtons = new HashMap<>(); - @Unique private IconButton eap$toggleSlotsButton; - @Unique private boolean eap$showSlots = false; // 默认由配置初始化 - @Unique private long eap$currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID + @Shadow(remap = false) @Final private AETextField searchOutField; @Shadow(remap = false) @Final private AETextField searchInField; @Shadow(remap = false) @Final private Set matchedStack; @Shadow(remap = false) @Final private Set matchedProvider; + @Shadow(remap = false) @Final private HashMultimap byGroup; @Shadow(remap = false) @Final private HashMap infoMap; @Shadow(remap = false) @Final private Scrollbar scrollbar; @@ -67,24 +66,28 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen @Shadow(remap = false) private int visibleRows; @Shadow(remap = false) @Final private HashMap highlightBtns; + /* ----- eap 自有字段 ----- */ + @Unique private final Map openUIButtons = new HashMap<>(); + @Unique private IconButton eap$toggleSlotsButton; + @Unique private boolean eap$showSlots = false; // 默认由配置初始化 + @Unique private long currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID + + // 按钮更新/缓存状态,避免每帧重建 + @Unique private boolean buttonsDirty = true; // 当列表或布局变化时置 true + @Unique private int lastScroll = Integer.MIN_VALUE; + @Unique private int lastRowsSize = Integer.MIN_VALUE; + @Unique private int lastVisibleRows = Integer.MIN_VALUE; + public GuiExPatternTerminalMixin(AEBaseMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } - /** - * 获取当前选择的样板供应器ID - */ - @Unique - public long getCurrentlyChoicePatternProvider() { - return eap$currentlyChoicePatterProvider; - } - /** * 设置当前选择的样板供应器ID */ @Unique public void setCurrentlyChoicePatternProvider(long id) { - this.eap$currentlyChoicePatterProvider = id; + this.currentlyChoicePatterProvider = id; } /** @@ -104,20 +107,14 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen // 检查是否是有效的编码样板 if (!clickedItem.isEmpty() && PatternDetailsHelper.isEncodedPattern(clickedItem)) { // 检查是否选择了样板供应器 - if (eap$currentlyChoicePatterProvider != -1) { + if (currentlyChoicePatterProvider != -1) { // 执行快速上传 this.eap$quickUploadPattern(hoveredSlot.getSlotIndex()); - // 取消默认的点击行为 cir.setReturnValue(true); } else { // 显示提示消息:请先选择一个样板供应器 - if (this.minecraft.player != null) { - this.minecraft.player.displayClientMessage( - Component.literal("ExtendedAE Plus: 请先选择一个样板供应器(点击GroupHeader旁的按钮)"), - true - ); - } + sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.select_provider_first")); } } } @@ -129,26 +126,30 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen */ @Unique private void eap$quickUploadPattern(int playerSlotIndex) { - if (this.minecraft.player != null) { - // 获取要上传的物品 - ItemStack itemToUpload = this.minecraft.player.getInventory().getItem(playerSlotIndex); - if (!itemToUpload.isEmpty() && PatternDetailsHelper.isEncodedPattern(itemToUpload)) { - EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("upload", playerSlotIndex, eap$currentlyChoicePatterProvider)); - } else { - this.minecraft.player.displayClientMessage( - Component.literal("❌ ExtendedAE Plus: 无效的样板物品"), - true - ); - } + if (this.minecraft.player == null) return; + + ItemStack itemToUpload = this.minecraft.player.getInventory().getItem(playerSlotIndex); + if (itemToUpload.isEmpty() || !PatternDetailsHelper.isEncodedPattern(itemToUpload)) { + sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.invalid_pattern")); + return; + } + + // 直接使用软依赖类发送包(EPPNetworkHandler + CGenericPacket 已在 classpath) + try { + EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("upload", playerSlotIndex, currentlyChoicePatterProvider)); + } catch (Throwable t) { + // 提示玩家网络支持缺失或版本不兼容 + sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.no_network_support")); } } /** - * 尝试打开指定行对应的样板供应器的 UI。 - * 该方法基于 GroupHeaderRow 获取分组信息,再获取该分组下的第一个 PatternContainerRecord, - * 并通过 serverId 获取 PatternProviderInfo 来发送 C2S 包打开目标供应器界面。 - * - * @param rowIndex 要操作的行索引 + * 尝试打开指定行对应的样板供应器的 UI + *

+ * 说明: + * - 通过 rows 获取 GroupHeaderRow(使用 Accessor 获取 group) + * - 通过 byGroup 获取对应 PatternContainerRecord 集合,拿第一个 record 的 serverId + * - 通过 infoMap 获取 PatternProviderInfo,发送 C2S 包打开目标容器界面 */ @Unique private void eap$tryOpenProviderUI(int rowIndex) { @@ -158,65 +159,44 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen PatternContainerGroup group = ((GuiExPatternTerminalGroupHeaderRowAccessor) headerRow).Group(); // 获取该组下的所有 PatternContainerRecord Set containers = byGroup.get(group); - if (containers == null || containers.isEmpty()) { - return; // 分组为空,无供应器 - } + if (containers == null || containers.isEmpty()) return; - // 取该组下第一个 PatternContainerRecord PatternContainerRecord firstRecord = containers.iterator().next(); - long serverId = firstRecord.getServerId(); // 获取供应器服务器 ID + long serverId = firstRecord.getServerId(); - // 通过 infoMap 获取供应器位置信息 - GuiExPatternTerminal.PatternProviderInfo patternProviderInfo = infoMap.get(serverId); - if (patternProviderInfo == null) { - // 如果没有位置信息,提示玩家 - if (this.minecraft != null && this.minecraft.player != null) { - this.minecraft.player.displayClientMessage( - Component.literal("未找到该供应器的位置信息,无法打开UI"), - true - ); - } - return; - } + GuiExPatternTerminal.PatternProviderInfo info = infoMap.get(serverId); + if (info == null) return; - // 获取位置信息和朝向 - BlockPos pos = patternProviderInfo.pos(); - Direction face = patternProviderInfo.face(); - ResourceKey playerWorld = patternProviderInfo.playerWorld(); + BlockPos pos = info.pos(); + Direction face = info.face(); + ResourceKey worldKey = info.playerWorld(); + if (pos == null || worldKey == null) return; - // 转换为 C2S 包所需类型 - long posLong = pos.asLong(); - ResourceLocation dimStr = playerWorld.location(); - int faceOrd = (face != null) ? - face.ordinal() : - -1; - - // 发送打开 UI 的 C2S 包 ModNetwork.CHANNEL.sendToServer(new OpenProviderUiC2SPacket( - posLong, - dimStr, - faceOrd + pos.asLong(), + worldKey.location(), + face != null ? face.ordinal() : -1 )); - } catch (Throwable ignored) { - // 静默失败,不影响界面操作 - } + } catch (Exception ignored) {} } - @Shadow - private void refreshList() {} - - @Shadow - private void resetScrollbar() {} + /* ----- Shadow 方法 ----- */ + @Shadow(remap = false) private void refreshList() {} + @Shadow(remap = false) private void resetScrollbar() {} + /* ----- 构造注入:创建切换按钮(只设置状态并触发一次 refresh) ----- */ @Inject(method = "", at = @At("TAIL"), remap = false) private void injectConstructor(CallbackInfo ci) { - // 根据配置初始化默认显示/隐藏状态 + // 初始化默认显示状态 this.eap$showSlots = ModConfig.INSTANCE.patternTerminalShowSlotsDefault; - // 创建切换槽位显示的按钮 - this.eap$toggleSlotsButton = new IconButton((b) -> { - this.eap$showSlots = !this.eap$showSlots; // 开关状态 - refreshList(); + // 创建切换槽位显示的按钮(只切换状态并触发一次 refresh) + this.eap$toggleSlotsButton = new IconButton((b) -> { + this.eap$showSlots = !this.eap$showSlots; + // 标记需要更新按钮与高亮映射 + this.buttonsDirty = true; + this.refreshList(); + this.resetScrollbar(); }) { @Override protected Icon getIcon() { @@ -231,148 +211,166 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen this.addToLeftToolbar(this.eap$toggleSlotsButton); } - /** - * 处理屏幕缩放(resize)后按钮位置未更新的问题: - * - 清理并移除现有的“打开UI”按钮 - * - 尝试重置滚动条并刷新列表 - * 缩放后的下一帧,drawFG 会基于新的 leftPos/topPos 重建与定位按钮 - */ - @Inject(method = "resize", at = @At("TAIL"), remap = false, require = 0) - private void eap$onResize(Minecraft mc, int width, int height, CallbackInfo ci) { - try { - // 移除并清理按钮,避免旧位置残留 - this.eap$openUIButtons.values().forEach(this::removeWidget); - this.eap$openUIButtons.clear(); - refreshList(); - resetScrollbar(); - } catch (Throwable ignored) { - } - } - @Inject(method = "init", at = @At("TAIL"), remap = false, require = 0) private void eap$onInit(CallbackInfo ci) { - // 清理旧的打开UI按钮 - this.eap$openUIButtons.values().forEach(this::removeWidget); - this.eap$openUIButtons.clear(); + // 清理旧的打开UI按钮并标记需要重建 + this.openUIButtons.values().forEach(this::removeWidget); + this.openUIButtons.clear(); + this.buttonsDirty = true; } @Inject(method = "refreshList", at = @At("HEAD"), remap = false) private void onRefreshListStart(CallbackInfo ci) { - // 更新按钮图标 + // 更新 toggle 按钮 tooltip 文本 if (this.eap$toggleSlotsButton != null) { this.eap$toggleSlotsButton.setTooltip(Tooltip.create(Component.translatable( - this.eap$showSlots ? - "gui.expatternprovider.hide_slots" : - "gui.expatternprovider.show_slots" + this.eap$showSlots ? "gui.expatternprovider.hide_slots" : "gui.expatternprovider.show_slots" ))); } - // 清理旧的打开UI按钮 - this.eap$openUIButtons.values().forEach(this::removeWidget); - this.eap$openUIButtons.clear(); + // 清理并标记需要重建 UI 按钮(但不在此处做重建) + this.openUIButtons.values().forEach(this::removeWidget); + this.openUIButtons.clear(); + this.buttonsDirty = true; } + /** + * refreshList 完成后,如果不显示 slots,则在 rows 上做“压缩”并尽量复用 highlightBtns 映射。 + * 这个实现会尽量复用已有 highlightBtns 的实例,避免无谓的对象重建。 + */ @Inject(method = "refreshList", at = @At("TAIL"), remap = false) private void onRefreshListEnd(CallbackInfo ci) { - - // 在refreshList结束后,根据showSlots状态过滤SlotsRow if (!this.eap$showSlots) { try { - // 创建新的索引映射 HashMap newHighlightBtns = new HashMap<>(); int newIndex = 0; - // 移除所有SlotsRow,只保留GroupHeaderRow,同时重新映射高亮按钮索引 + // 遍历 rows,保留 GroupHeaderRow 并尝试复用 highlightBtns(原 index -> 按钮) for (int i = 0; i < rows.size(); i++) { Object row = rows.get(i); String className = row.getClass().getSimpleName(); if (className.equals("GroupHeaderRow")) { - // 保留GroupHeaderRow,并重新映射对应的高亮按钮 @SuppressWarnings("unchecked") ArrayList typedRows = (ArrayList) rows; typedRows.set(newIndex, row); - // 查找原来在这个位置的高亮按钮 - // 原始代码中,高亮按钮的索引是在添加GroupHeaderRow之后、添加第一个SlotsRow之前设置的 - // 所以按钮的索引指向的是第一个SlotsRow的位置 - // 我们需要查找索引为 i+1 的按钮(第一个SlotsRow的位置) + // 原 highlightBtns 在原实现中是放在 GroupHeaderRow 之后第一个 SlotsRow 的 index(i+1) + // 尝试复用原映射中 i+1 的按钮(若存在) if (highlightBtns.containsKey(i + 1)) { HighlightButton button = highlightBtns.get(i + 1); newHighlightBtns.put(newIndex, button); } newIndex++; - } else if (className.equals("SlotsRow")) { - // 不保留SlotsRow,也不增加newIndex } + // SlotsRow:跳过,不保留 } - // 移除多余的行 + // 移除多余行 while (rows.size() > newIndex) { rows.remove(rows.size() - 1); } - // 更新highlightBtns + // 更新 highlightBtns:清理旧 map 并复用 new map highlightBtns.clear(); highlightBtns.putAll(newHighlightBtns); - // 强制刷新滚动条 - resetScrollbar(); + // 强制刷新滚动条(一次) + this.resetScrollbar(); } catch (Exception ignored) { } } + // 标记按钮需要重建(因为 rows 结构可能已改变) + this.buttonsDirty = true; } + /** + * drawFG 优化:仅在需要时创建/移除按钮;每帧只更新可见按钮的位置与可见性。 + */ @Inject(method = "drawFG", at = @At("TAIL"), remap = false) private void eap$afterDrawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY, CallbackInfo ci) { - // 动态放置/创建每个组标题后的“打开UI”按钮 try { int currentScroll = scrollbar.getCurrentScroll(); + int rowsSize = rows.size(); + int visRows = this.visibleRows; - // 先隐藏旧按钮,避免残留 - for (Button b : this.eap$openUIButtons.values()) { - b.visible = false; + // 当列表或滚动或 visibleRows 发生变化时,重建或清理按钮(按需) + boolean needFullUpdate = this.buttonsDirty + || currentScroll != lastScroll + || rowsSize != lastRowsSize + || visRows != lastVisibleRows; + + if (needFullUpdate) { + // 清理已经超出范围或者已不存在的按钮 + openUIButtons.entrySet().removeIf(entry -> { + int idx = entry.getKey(); + if (idx < 0 || idx >= rowsSize) { + removeWidget(entry.getValue()); + return true; + } + return false; + }); + + // 为当前可见窗口内的 GroupHeaderRow 创建按钮(如果不存在) + for (int i = 0; i < visRows; i++) { + int rowIndex = currentScroll + i; + if (rowIndex < 0 || rowIndex >= rowsSize) continue; + Object row = rows.get(rowIndex); + if (!row.getClass().getSimpleName().equals("GroupHeaderRow")) continue; + + // 计算按钮位置(与原实现保持一致) + int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 11; + int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT - 2; + + Button btn = openUIButtons.get(rowIndex); + if (btn == null) { + btn = Button.builder( + Component.literal("UI"), + (b) -> eap$tryOpenProviderUI(rowIndex) + ).size(14, 12).build(); + btn.setTooltip(Tooltip.create(Component.translatable("extendedae_plus.screen.open_provider_ui"))); + openUIButtons.put(rowIndex, btn); + this.addRenderableWidget(btn); + } + btn.setPosition(bx, by); + btn.visible = true; + } + + // 将不在当前可见窗口内的按钮隐藏 + for (Map.Entry e : openUIButtons.entrySet()) { + int idx = e.getKey(); + if (idx < currentScroll || idx >= currentScroll + visRows) { + e.getValue().visible = false; + } + } + + // 更新缓存状态 + this.lastScroll = currentScroll; + this.lastRowsSize = rowsSize; + this.lastVisibleRows = visRows; + this.buttonsDirty = false; + } else { + // 每帧只更新可见按钮位置(可能因为 leftPos/topPos 动态变化,比如移动窗口) + for (int i = 0; i < visRows; i++) { + int rowIndex = currentScroll + i; + Button btn = openUIButtons.get(rowIndex); + if (btn == null) continue; + int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 11; + int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT - 2; + btn.setPosition(bx, by); + btn.visible = true; + } } - - for (int i = 0; i < visibleRows; i++) { - int rowIndex = currentScroll + i; - if (rowIndex < 0 || rowIndex >= rows.size()) { - continue; - } - Object row = rows.get(rowIndex); - if (!row.getClass().getSimpleName().equals("GroupHeaderRow")) { - continue; - } - - // 放置按钮:位于名称文本右侧,与原类 choiceButton 锚点相邻,向右偏移 20px - int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 11; - int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT - 2; - - Button btn = eap$openUIButtons.get(rowIndex); - if (btn == null) { - btn = Button.builder(Component.literal("UI"), (b) -> { - eap$tryOpenProviderUI(rowIndex); - }).size(14, 12).build(); - btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面"))); - eap$openUIButtons.put(rowIndex, btn); - this.addRenderableWidget(btn); - } - btn.setPosition(bx, by); - btn.visible = true; - } - // 生产环境移除调试日志 } catch (Throwable ignored) { } - // 原有的搜索高亮逻辑 - // 仅当任一搜索框非空时绘制叠加层(与原版行为保持一致) + // 原有的搜索高亮绘制(仅在搜索激活时绘制) boolean searchActive = (this.searchOutField != null && !this.searchOutField.getValue().isEmpty()) || (this.searchInField != null && !this.searchInField.getValue().isEmpty()); if (!searchActive) { return; } - // 使用 GuiUtil 的通用绘制方法绘制槽位高亮(包含彩虹流转效果) GuiUtil.drawPatternSlotHighlights(guiGraphics, this.menu.slots, this.matchedStack, this.matchedProvider); } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java index b3206d2..c8666a5 100644 --- a/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java @@ -7,8 +7,8 @@ import appeng.api.stacks.AEKey; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.me.service.CraftingService; +import appeng.menu.me.crafting.CraftingCPUMenu; import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; -import com.mojang.logging.LogUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.FriendlyByteBuf; @@ -52,15 +52,14 @@ public class CraftingMonitorJumpC2SPacket { ServerPlayer player = context.getSender(); if (player == null) return; - // 必须在 CraftingCPU 界面内 - if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) { + if (!(player.containerMenu instanceof CraftingCPUMenu menu)) { return; } // 通过菜单 target(可能是 BlockEntity/Part/ItemHost)按 IActionHost 获取 Grid IGrid grid = null; - Object target = ((appeng.menu.AEBaseMenu) menu).getTarget(); + Object target = menu.getTarget(); if (target instanceof IActionHost host && host.getActionableNode() != null) { grid = host.getActionableNode().getGrid(); } @@ -82,9 +81,7 @@ public class CraftingMonitorJumpC2SPacket { // 2) 遍历提供该样板的 Provider,优先 PatternProviderLogic for (var pattern : patterns) { var providers = craftingService.getProviders(pattern); - int providerCount = 0; for (var provider : providers) { - providerCount++; if (provider instanceof PatternProviderLogic ppl) { // 使用 accessor 获取 host(受保护字段通过 accessor 访问) PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host(); 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 f0dd9c9..14d33f5 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -50,6 +50,8 @@ "extendedae_plus.upload_to_matrix.fail_not_crafting": "Only crafting patterns are supported; processing patterns will be ignored", "extendedae_plus.upload_to_matrix.fail_no_matrix": "No formed assembly matrix found in the current network", "extendedae_plus.upload_to_matrix.fail_full": "The assembly matrix pattern inventory is full or cannot insert", + "extendedae_plus.upload_to_matrix.repetition": "The Assembly Matrix already contains the same pattern. Upload skipped and blank pattern returned.", + "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", @@ -63,6 +65,11 @@ "extendedae_plus.screen.upload.mapping_deleted": "Deleted %d mapping(s), name = %s", "extendedae_plus.screen.upload.mapping_not_found": "No mapping found for name '%s'", "extendedae_plus.screen.upload.name": "Mapping Name", + "extendedae_plus.screen.upload.select_provider_first":"Please click the button on the left to select a pattern provider first", + "extendedae_plus.screen.upload.invalid_pattern": "Invalid Pattern", + "extendedae_plus.screen.upload.no_network_support": "ExtendedAE network support not found (may be missing or incompatible version)", + + "extendedae_plus.screen.open_provider_ui": "Open the target container UI of this provider", "extendedae_plus.button.choose_provider": "Upload Pattern", "extendedae_plus.pattern.hovertext.player": "Encoded by %s", 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 c87b8cf..453d94a 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -66,6 +66,11 @@ "extendedae_plus.screen.upload.mapping_deleted": "已删除 %d 条映射,名称 = %s", "extendedae_plus.screen.upload.mapping_not_found": "未找到名称为 '%s' 的映射", "extendedae_plus.screen.upload.name": "映射名", + "extendedae_plus.screen.upload.select_provider_first": "请先点击左侧按钮选择一个样板供应器", + "extendedae_plus.screen.upload.invalid_pattern": "无效样板", + "extendedae_plus.screen.upload.no_network_support": "未找到 ExtendedAE 网络支持(可能未安装或版本不兼容)", + + "extendedae_plus.screen.open_provider_ui": "打开该供应器目标容器的界面", "extendedae_plus.button.choose_provider":"上传样板", "extendedae_plus.pattern.hovertext.player": "由 %s 编码",