diff --git a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java index 75d96bb..7266314 100644 --- a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java +++ b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java @@ -149,6 +149,12 @@ public class ProviderSelectScreen extends Screen { .build(); this.addRenderableWidget(addMap); + // 删除映射(按中文值精确匹配删除)按钮 + Button delByCn = Button.builder(Component.literal("删除映射"), b -> deleteMappingByCnFromUI()) + .bounds(centerX + 240, navY + 30, 60, 20) + .build(); + this.addRenderableWidget(delByCn); + // 关闭按钮 Button close = Button.builder(Component.translatable("gui.cancel"), b -> onClose()) .bounds(centerX - 40, navY + 30, 80, 20) @@ -353,4 +359,22 @@ public class ProviderSelectScreen extends Screen { if (player != null) player.sendSystemMessage(Component.literal("写入映射失败")); } } + + // 使用中文值精确匹配删除映射 + private void deleteMappingByCnFromUI() { + String val = cnInput == null ? "" : cnInput.getValue().trim(); + var player = Minecraft.getInstance().player; + if (val.isEmpty()) { + if (player != null) player.sendSystemMessage(Component.literal("请输入中文名称后再删除映射")); + return; + } + int removed = com.extendedae_plus.util.ExtendedAEPatternUploadUtil.removeMappingsByCnValue(val); + if (removed > 0) { + if (player != null) player.sendSystemMessage(Component.literal("已删除 " + removed + " 条映射,中文= " + val)); + applyFilter(); + needsRefresh = true; + } else { + if (player != null) player.sendSystemMessage(Component.literal("未找到中文为 '" + val + "' 的映射")); + } + } } diff --git a/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java b/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java index ce242f1..820b3ce 100644 --- a/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java @@ -33,17 +33,17 @@ public abstract class EncodePatternTransferHandlerMixin { // 仅记录处理配方(非 3x3 合成) if (EncodingHelper.isSupportedCraftingRecipe(recipe)) return; name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(recipe); - } else if (recipeBase instanceof com.gregtechceu.gtceu.api.recipe.GTRecipe gtRecipe) { - // GTCEu 专用:从 GTRecipeType 提取注册ID并映射为中文或path - name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(gtRecipe); + } else if (recipeBase != null && + "com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) { + // 反射路径:GTCEu 专用,从 GTRecipeType 提取注册ID并映射为中文或path + name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(recipeBase); } else if ("com.gregtechceu.gtceu.integration.jei.recipe.GTRecipeWrapper".equals(recipeBase.getClass().getName())) { // 通过反射处理 GTCEu JEI 包装类,避免硬依赖 try { var field = recipeBase.getClass().getField("recipe"); // public final GTRecipe recipe; Object inner = field.get(recipeBase); - if (inner instanceof com.gregtechceu.gtceu.api.recipe.GTRecipe gtRecipeInner) { - name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(gtRecipeInner); - } + // 反射路径:将内部 GTRecipe 以 Object 传入 + name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(inner); } catch (Throwable ignored) { // 反射失败则继续走通用回退 } diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 5b83c6a..ef505b8 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -179,6 +179,69 @@ public class ExtendedAEPatternUploadUtil { } } + /** + * 按中文值精确匹配删除映射(支持别名与完整ID)。 + * 返回删除的条目数量。 + */ + public static synchronized int removeMappingsByCnValue(String cnValue) { + if (cnValue == null) return 0; + String target = cnValue.trim(); + if (target.isEmpty()) return 0; + try { + Path cfgDir = FMLPaths.CONFIGDIR.get(); + Path cfgPath = cfgDir.resolve(CONFIG_RELATIVE); + if (!Files.exists(cfgPath)) { + return 0; + } + String json = Files.readString(cfgPath); + JsonObject obj = GSON.fromJson(json, JsonObject.class); + if (obj == null) return 0; + + java.util.List toRemove = new java.util.ArrayList<>(); + for (java.util.Map.Entry e : obj.entrySet()) { + JsonElement v = e.getValue(); + if (v != null && v.isJsonPrimitive()) { + String name = v.getAsString(); + if (target.equals(name)) { + toRemove.add(e.getKey()); + } + } + } + if (toRemove.isEmpty()) return 0; + + // 从 JSON 中移除 + for (String k : toRemove) { + obj.remove(k); + } + Files.createDirectories(cfgPath.getParent()); + Files.writeString(cfgPath, GSON.toJson(obj)); + + // 同步移除内存映射 + for (String k : toRemove) { + if (k.contains(":")) { + try { + ResourceLocation rl = new ResourceLocation(k); + // 仅当值匹配才移除(双重保险) + String cur = CUSTOM_NAMES.get(rl); + if (target.equals(cur)) { + CUSTOM_NAMES.remove(rl); + } + } catch (Exception ignored) {} + } else { + // 别名按小写存放 + String lower = k.toLowerCase(); + String cur = CUSTOM_ALIASES.get(lower); + if (target.equals(cur)) { + CUSTOM_ALIASES.remove(lower); + } + } + } + return toRemove.size(); + } catch (IOException e) { + return 0; + } + } + public static String mapRecipeTypeToCn(Recipe recipe) { if (recipe == null) return null; RecipeType type = recipe.getType(); @@ -255,6 +318,35 @@ public class ExtendedAEPatternUploadUtil { } } + /** + * 仅使用反射的 GTCEu GTRecipe -> 搜索关键字(避免在运行时直接引用 GTCEu 类)。 + * 逻辑与 {@link #mapGTCEuRecipeToSearchKey(com.gregtechceu.gtceu.api.recipe.GTRecipe)} 等价。 + */ + public static String mapGTCEuRecipeToSearchKey(Object gtRecipeObj) { + if (gtRecipeObj == null) return null; + try { + // 通过反射调用 getType(),其 toString() 应返回 registryName,即 namespace:path + java.lang.reflect.Method mGetType = gtRecipeObj.getClass().getMethod("getType"); + Object typeObj = mGetType.invoke(gtRecipeObj); + String idStr = String.valueOf(typeObj); + if (idStr == null || idStr.isBlank()) return null; + ResourceLocation rl = new ResourceLocation(idStr); + // 1) 别名优先(使用 path 作为最终搜索关键字) + String path = rl.getPath(); + if (path != null) { + String alias = CUSTOM_ALIASES.get(path.toLowerCase()); + if (alias != null && !alias.isBlank()) return alias; + } + // 2) 再查完整ID映射 + String custom = CUSTOM_NAMES.get(rl); + if (custom != null && !custom.isBlank()) return custom; + // 3) 默认返回 path 作为搜索关键字 + return (path != null && !path.isBlank()) ? path : idStr; + } catch (Throwable t) { + return null; + } + } + /** * 当 JEI 传入的 recipeBase 不是原版 Recipe 时,根据类的包名/类名推导一个尽量可用的搜索关键字。 * 例如:"moe.gregtech.recipe.SomeAssemblerRecipe" -> "gtceu assembler"