抽离配方读取
This commit is contained in:
parent
c42f429b0a
commit
e109bd9ef9
|
|
@ -2,7 +2,7 @@ package com.extendedae_plus.client.screen;
|
||||||
|
|
||||||
import com.extendedae_plus.init.ModNetwork;
|
import com.extendedae_plus.init.ModNetwork;
|
||||||
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
||||||
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
|
import com.extendedae_plus.util.uploadPattern.RecipeTypeNameConfig;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.EditBox;
|
import net.minecraft.client.gui.components.EditBox;
|
||||||
|
|
@ -67,11 +67,11 @@ public class ProviderSelectScreen extends Screen {
|
||||||
this.emptySlots = emptySlots;
|
this.emptySlots = emptySlots;
|
||||||
// 如果有来自 JEI 的最近处理名称,则作为初始查询
|
// 如果有来自 JEI 的最近处理名称,则作为初始查询
|
||||||
try {
|
try {
|
||||||
String recent = ExtendedAEPatternUploadUtil.lastProcessingName;
|
String recent = RecipeTypeNameConfig.lastProcessingName;
|
||||||
if (recent != null && !recent.isBlank()) {
|
if (recent != null && !recent.isBlank()) {
|
||||||
this.query = recent;
|
this.query = recent;
|
||||||
// 用后即清空,避免污染下次
|
// 用后即清空,避免污染下次
|
||||||
ExtendedAEPatternUploadUtil.lastProcessingName = null;
|
RecipeTypeNameConfig.lastProcessingName = null;
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
buildGroups();
|
buildGroups();
|
||||||
|
|
@ -212,7 +212,7 @@ public class ProviderSelectScreen extends Screen {
|
||||||
|
|
||||||
private void reloadMapping() {
|
private void reloadMapping() {
|
||||||
try {
|
try {
|
||||||
ExtendedAEPatternUploadUtil.loadRecipeTypeNames();
|
RecipeTypeNameConfig.loadRecipeTypeNames();
|
||||||
sendPlayerMessage(Component.translatable("extendedae_plus.screen.reload_mapping_success"));
|
sendPlayerMessage(Component.translatable("extendedae_plus.screen.reload_mapping_success"));
|
||||||
// 重载后不强制刷新筛选,但如需立即应用到名称匹配,可手动编辑搜索框或翻页
|
// 重载后不强制刷新筛选,但如需立即应用到名称匹配,可手动编辑搜索框或翻页
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
@ -473,7 +473,7 @@ public class ProviderSelectScreen extends Screen {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean ok = ExtendedAEPatternUploadUtil.addOrUpdateAliasMapping(key, val);
|
boolean ok = RecipeTypeNameConfig.addOrUpdateAliasMapping(key, val);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.mapping_added", key, val));
|
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.mapping_added", key, val));
|
||||||
// 将刚添加的中文名写入搜索框,作为当前查询
|
// 将刚添加的中文名写入搜索框,作为当前查询
|
||||||
|
|
@ -497,7 +497,7 @@ public class ProviderSelectScreen extends Screen {
|
||||||
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.enter_cn_name_delete"));
|
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.enter_cn_name_delete"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int removed = ExtendedAEPatternUploadUtil.removeMappingsByCnValue(val);
|
int removed = RecipeTypeNameConfig.removeMappingsByCnValue(val);
|
||||||
if (removed > 0) {
|
if (removed > 0) {
|
||||||
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.mapping_deleted", removed, val));
|
sendPlayerMessage(Component.translatable("extendedae_plus.screen.upload.mapping_deleted", removed, val));
|
||||||
applyFilter();
|
applyFilter();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.jei;
|
||||||
import appeng.integration.modules.jei.transfer.EncodePatternTransferHandler;
|
import appeng.integration.modules.jei.transfer.EncodePatternTransferHandler;
|
||||||
import appeng.integration.modules.jeirei.EncodingHelper;
|
import appeng.integration.modules.jeirei.EncodingHelper;
|
||||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||||
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
|
import com.extendedae_plus.util.uploadPattern.RecipeTypeNameConfig;
|
||||||
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
|
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
|
||||||
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
|
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
|
@ -32,27 +32,27 @@ public abstract class EncodePatternTransferHandlerMixin {
|
||||||
if (recipeBase instanceof Recipe<?> recipe) {
|
if (recipeBase instanceof Recipe<?> recipe) {
|
||||||
// 仅记录处理配方(非 3x3 合成)
|
// 仅记录处理配方(非 3x3 合成)
|
||||||
if (EncodingHelper.isSupportedCraftingRecipe(recipe)) return;
|
if (EncodingHelper.isSupportedCraftingRecipe(recipe)) return;
|
||||||
name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(recipe);
|
name = RecipeTypeNameConfig.mapRecipeTypeToSearchKey(recipe);
|
||||||
} else if (recipeBase != null &&
|
} else if (recipeBase != null &&
|
||||||
"com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) {
|
"com.gregtechceu.gtceu.api.recipe.GTRecipe".equals(recipeBase.getClass().getName())) {
|
||||||
// 反射路径:GTCEu 专用,从 GTRecipeType 提取注册ID并映射为中文或path
|
// 反射路径:GTCEu 专用,从 GTRecipeType 提取注册ID并映射为中文或path
|
||||||
name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(recipeBase);
|
name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(recipeBase);
|
||||||
} else if ("com.gregtechceu.gtceu.integration.jei.recipe.GTRecipeWrapper".equals(recipeBase.getClass().getName())) {
|
} else if ("com.gregtechceu.gtceu.integration.jei.recipe.GTRecipeWrapper".equals(recipeBase.getClass().getName())) {
|
||||||
// 通过反射处理 GTCEu JEI 包装类,避免硬依赖
|
// 通过反射处理 GTCEu JEI 包装类,避免硬依赖
|
||||||
try {
|
try {
|
||||||
var field = recipeBase.getClass().getField("recipe"); // public final GTRecipe recipe;
|
var field = recipeBase.getClass().getField("recipe"); // public final GTRecipe recipe;
|
||||||
Object inner = field.get(recipeBase);
|
Object inner = field.get(recipeBase);
|
||||||
// 反射路径:将内部 GTRecipe 以 Object 传入
|
// 反射路径:将内部 GTRecipe 以 Object 传入
|
||||||
name = ExtendedAEPatternUploadUtil.mapGTCEuRecipeToSearchKey(inner);
|
name = RecipeTypeNameConfig.mapGTCEuRecipeToSearchKey(inner);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
// 反射失败则继续走通用回退
|
// 反射失败则继续走通用回退
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 非原版 Recipe<?> 的 JEI 条目,尝试从类名/包名推导关键词
|
// 非原版 Recipe<?> 的 JEI 条目,尝试从类名/包名推导关键词
|
||||||
name = ExtendedAEPatternUploadUtil.deriveSearchKeyFromUnknownRecipe(recipeBase);
|
name = RecipeTypeNameConfig.deriveSearchKeyFromUnknownRecipe(recipeBase);
|
||||||
}
|
}
|
||||||
if (name != null && !name.isBlank()) {
|
if (name != null && !name.isBlank()) {
|
||||||
ExtendedAEPatternUploadUtil.setLastProcessingName(name);
|
RecipeTypeNameConfig.setLastProcessingName(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,329 +16,23 @@ import appeng.util.inv.FilteredInternalInventory;
|
||||||
import appeng.util.inv.filter.IAEItemFilter;
|
import appeng.util.inv.filter.IAEItemFilter;
|
||||||
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
|
import com.extendedae_plus.mixin.ae2.accessor.PatternEncodingTermMenuAccessor;
|
||||||
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
|
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase;
|
||||||
import com.google.gson.*;
|
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.crafting.Recipe;
|
|
||||||
import net.minecraft.world.item.crafting.RecipeType;
|
|
||||||
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
||||||
import net.minecraftforge.fml.loading.FMLPaths;
|
|
||||||
import net.minecraftforge.items.IItemHandler;
|
import net.minecraftforge.items.IItemHandler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Files;
|
import java.util.ArrayList;
|
||||||
import java.nio.file.Path;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.extendedae_plus.util.GlobalSendMessage.sendPlayerMessage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExtendedAE扩展样板管理终端专用的样板上传工具类
|
* ExtendedAE扩展样板管理终端专用的样板上传工具类
|
||||||
* 兼容ExtendedAE的ContainerExPatternTerminal和原版AE2的PatternAccessTermMenu
|
* 兼容ExtendedAE的ContainerExPatternTerminal和原版AE2的PatternAccessTermMenu
|
||||||
*/
|
*/
|
||||||
public class ExtendedAEPatternUploadUtil {
|
public class ExtendedAEPatternUploadUtil {
|
||||||
|
|
||||||
// --------------------------- 配置:RecipeType 中文名称映射 ---------------------------
|
|
||||||
private static final String CONFIG_RELATIVE = "extendedae_plus/recipe_type_names.json";
|
|
||||||
private static final Map<ResourceLocation, String> CUSTOM_NAMES = new ConcurrentHashMap<>();
|
|
||||||
// 允许使用最终搜索关键字(通常为 path 或自定义短语)作为键,例如:"assembler": "组装机"
|
|
||||||
private static final Map<String, String> CUSTOM_ALIASES = new ConcurrentHashMap<>();
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
loadRecipeTypeNames();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// 安静失败,使用内置映射
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从配置文件加载 RecipeType → 中文名称映射。文件不存在则生成模板。
|
|
||||||
* 同时支持“别名”形式:不含冒号的键会被视为最终搜索关键字(大小写不敏感),如:
|
|
||||||
* {
|
|
||||||
* "assembler": "组装机"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
public static synchronized void loadRecipeTypeNames() {
|
|
||||||
try {
|
|
||||||
Path cfgDir = FMLPaths.CONFIGDIR.get();
|
|
||||||
Path cfgPath = cfgDir.resolve(CONFIG_RELATIVE);
|
|
||||||
if (!Files.exists(cfgPath)) {
|
|
||||||
// 创建目录并写入模板
|
|
||||||
Files.createDirectories(cfgPath.getParent());
|
|
||||||
JsonObject tmpl = new JsonObject();
|
|
||||||
// 提供一些常见原版默认(仅作为示例,实际仍以内置 switch 为兜底)
|
|
||||||
tmpl.addProperty("minecraft:smelting", "熔炉");
|
|
||||||
tmpl.addProperty("minecraft:blasting", "高炉");
|
|
||||||
tmpl.addProperty("minecraft:smoking", "烟熏");
|
|
||||||
tmpl.addProperty("minecraft:campfire_cooking", "营火");
|
|
||||||
// GTCEu 示例占位
|
|
||||||
tmpl.addProperty("gtceu:assembler", "组装机");
|
|
||||||
tmpl.addProperty("gtceu:arc_furnace", "电弧炉");
|
|
||||||
tmpl.addProperty("gtceu:chemical_reactor", "化学反应器");
|
|
||||||
// 也支持别名(最终搜索关键字)形式,例如:
|
|
||||||
tmpl.addProperty("assembler", "组装机");
|
|
||||||
Files.writeString(cfgPath, GSON.toJson(tmpl));
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = Files.readString(cfgPath);
|
|
||||||
JsonObject obj = GSON.fromJson(json, JsonObject.class);
|
|
||||||
Map<ResourceLocation, String> map = new HashMap<>();
|
|
||||||
Map<String, String> alias = new HashMap<>();
|
|
||||||
if (obj != null) {
|
|
||||||
for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
|
|
||||||
String k = e.getKey();
|
|
||||||
JsonElement v = e.getValue();
|
|
||||||
if (v != null && v.isJsonPrimitive()) {
|
|
||||||
String name = v.getAsString();
|
|
||||||
if (name == null || name.isBlank()) continue;
|
|
||||||
if (k.contains(":")) {
|
|
||||||
// 形如 namespace:path
|
|
||||||
try {
|
|
||||||
ResourceLocation rl = new ResourceLocation(k);
|
|
||||||
map.put(rl, name);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
} else {
|
|
||||||
// 视为别名:最终搜索关键字(大小写不敏感)
|
|
||||||
alias.put(k.toLowerCase(), name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CUSTOM_NAMES.clear();
|
|
||||||
CUSTOM_NAMES.putAll(map);
|
|
||||||
CUSTOM_ALIASES.clear();
|
|
||||||
CUSTOM_ALIASES.putAll(alias);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最近一次通过 JEI 填充到编码终端的“处理配方”的中文名称(如:烧炼/高炉/烟熏...)
|
|
||||||
public static volatile String lastProcessingName = null;
|
|
||||||
|
|
||||||
public static void setLastProcessingName(String name) {
|
|
||||||
lastProcessingName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 向配置中新增或更新“别名 -> 中文”映射,并刷新内存映射。
|
|
||||||
* 仅用于非原版(或希望使用最终搜索关键字)场景。
|
|
||||||
*
|
|
||||||
* @param aliasKey 最终搜索关键字(不含冒号),大小写不敏感
|
|
||||||
* @param cnValue 中文名称
|
|
||||||
* @return 是否写入成功
|
|
||||||
*/
|
|
||||||
public static synchronized boolean addOrUpdateAliasMapping(String aliasKey, String cnValue) {
|
|
||||||
if (aliasKey == null || aliasKey.isBlank() || cnValue == null || cnValue.isBlank()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Path cfgDir = FMLPaths.CONFIGDIR.get();
|
|
||||||
Path cfgPath = cfgDir.resolve(CONFIG_RELATIVE);
|
|
||||||
if (!Files.exists(cfgPath)) {
|
|
||||||
// 若文件不存在,先创建模板
|
|
||||||
loadRecipeTypeNames();
|
|
||||||
}
|
|
||||||
JsonObject obj;
|
|
||||||
if (Files.exists(cfgPath)) {
|
|
||||||
String json = Files.readString(cfgPath);
|
|
||||||
obj = GSON.fromJson(json, JsonObject.class);
|
|
||||||
if (obj == null) obj = new JsonObject();
|
|
||||||
} else {
|
|
||||||
obj = new JsonObject();
|
|
||||||
}
|
|
||||||
String key = aliasKey.trim();
|
|
||||||
// 仅允许作为别名写入(不含冒号),如包含冒号,仍按原样写入,但推荐别名
|
|
||||||
obj.addProperty(key, cnValue);
|
|
||||||
Files.createDirectories(cfgPath.getParent());
|
|
||||||
Files.writeString(cfgPath, GSON.toJson(obj));
|
|
||||||
|
|
||||||
// 更新内存映射
|
|
||||||
if (key.contains(":")) {
|
|
||||||
try {
|
|
||||||
ResourceLocation rl = new ResourceLocation(key);
|
|
||||||
CUSTOM_NAMES.put(rl, cnValue);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
} else {
|
|
||||||
CUSTOM_ALIASES.put(key.toLowerCase(), cnValue);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (JsonSyntaxException e) {
|
|
||||||
sendPlayerMessage(Component.literal("ExtendedAE_Plus: 配置文件解析失败, " + e.getMessage()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按中文值精确匹配删除映射(支持别名与完整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<String> toRemove = new java.util.ArrayList<>();
|
|
||||||
for (java.util.Map.Entry<String, JsonElement> 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 (JsonSyntaxException e) {
|
|
||||||
sendPlayerMessage(Component.literal("ExtendedAE_Plus: 配置文件解析失败, " + e.getMessage()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 供搜索使用的关键字映射:
|
|
||||||
* - 有中文映射则返回中文;
|
|
||||||
* - 否则返回配方类型的 path(不含命名空间),例如 assembler。
|
|
||||||
*/
|
|
||||||
public static String mapRecipeTypeToSearchKey(Recipe<?> recipe) {
|
|
||||||
if (recipe == null) return null;
|
|
||||||
RecipeType<?> type = recipe.getType();
|
|
||||||
ResourceLocation key = BuiltInRegistries.RECIPE_TYPE.getKey(type);
|
|
||||||
if (key == null) return null;
|
|
||||||
// 先查别名(按 path 匹配)
|
|
||||||
String alias = CUSTOM_ALIASES.get(key.getPath().toLowerCase());
|
|
||||||
if (alias != null && !alias.isBlank()) return alias;
|
|
||||||
// 再查完整ID映射
|
|
||||||
String custom = CUSTOM_NAMES.get(key);
|
|
||||||
if (custom != null && !custom.isBlank()) {
|
|
||||||
return custom;
|
|
||||||
}
|
|
||||||
return key.getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅使用反射的 GTCEu GTRecipe -> 搜索关键字(避免在运行时直接引用 GTCEu 类)。
|
|
||||||
*/
|
|
||||||
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"
|
|
||||||
*/
|
|
||||||
public static String deriveSearchKeyFromUnknownRecipe(Object recipeBase) {
|
|
||||||
if (recipeBase == null) return null;
|
|
||||||
try {
|
|
||||||
Class<?> cls = recipeBase.getClass();
|
|
||||||
String simple = cls.getSimpleName();
|
|
||||||
String pkg = cls.getName();
|
|
||||||
|
|
||||||
String ns = null;
|
|
||||||
String lower = pkg.toLowerCase();
|
|
||||||
if (lower.contains("gtceu")) ns = "gtceu";
|
|
||||||
else if (lower.contains("gregtech")) ns = "gregtech";
|
|
||||||
else if (lower.contains("projecte")) ns = "projecte";
|
|
||||||
else if (lower.contains("create")) ns = "create";
|
|
||||||
else if (lower.contains("immersiveengineering")) ns = "immersive";
|
|
||||||
|
|
||||||
String token = toSearchToken(simple);
|
|
||||||
String key;
|
|
||||||
if (ns != null && token != null && !token.isBlank()) key = ns + " " + token;
|
|
||||||
else key = token != null && !token.isBlank() ? token : ns;
|
|
||||||
if (key == null || key.isBlank()) return null;
|
|
||||||
// 尝试别名映射(大小写不敏感)
|
|
||||||
String alias = CUSTOM_ALIASES.get(key.toLowerCase());
|
|
||||||
return (alias != null && !alias.isBlank()) ? alias : key;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toSearchToken(String simpleName) {
|
|
||||||
if (simpleName == null || simpleName.isBlank()) return null;
|
|
||||||
// 去掉常见后缀
|
|
||||||
String s = simpleName
|
|
||||||
.replaceAll("Recipe$", "")
|
|
||||||
.replaceAll("Recipes$", "")
|
|
||||||
.replaceAll("Category$", "")
|
|
||||||
.replaceAll("JEI$", "");
|
|
||||||
// 驼峰转空格并小写
|
|
||||||
s = s.replaceAll("(?<!^)([A-Z])", " $1").toLowerCase();
|
|
||||||
// 取首个关键词
|
|
||||||
s = s.trim();
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取玩家当前的样板访问终端菜单(支持ExtendedAE和原版AE2)
|
* 获取玩家当前的样板访问终端菜单(支持ExtendedAE和原版AE2)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
package com.extendedae_plus.util.uploadPattern;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.crafting.Recipe;
|
||||||
|
import net.minecraft.world.item.crafting.RecipeType;
|
||||||
|
import net.minecraftforge.fml.loading.FMLPaths;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static com.extendedae_plus.util.GlobalSendMessage.sendPlayerMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责配置文件 extendedae_plus/recipe_type_names.json 的加载与写入,
|
||||||
|
* 以及 recipeType -> 中文名称 / 搜索关键字 的映射逻辑。
|
||||||
|
*/
|
||||||
|
public final class RecipeTypeNameConfig {
|
||||||
|
|
||||||
|
private RecipeTypeNameConfig() {}
|
||||||
|
|
||||||
|
private static final String CONFIG_RELATIVE = "extendedae_plus/recipe_type_names.json";
|
||||||
|
private static final Map<ResourceLocation, String> CUSTOM_NAMES = new ConcurrentHashMap<>();
|
||||||
|
// 允许使用最终搜索关键字(通常为 path 或自定义短语)作为键,例如:"assembler": "组装机"
|
||||||
|
private static final Map<String, String> CUSTOM_ALIASES = new ConcurrentHashMap<>();
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
loadRecipeTypeNames();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// 安静失败,使用内置映射
|
||||||
|
sendPlayerMessage(Component.literal("ExtendedAE_Plus: 配置文件解析失败, " + t.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从配置文件加载 RecipeType → 中文名称映射。文件不存在则生成模板。
|
||||||
|
* 同时支持“别名”形式:不含冒号的键会被视为最终搜索关键字(大小写不敏感),如:
|
||||||
|
* {
|
||||||
|
* "assembler": "组装机"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public static synchronized void loadRecipeTypeNames() {
|
||||||
|
try {
|
||||||
|
Path cfgDir = FMLPaths.CONFIGDIR.get();
|
||||||
|
Path cfgPath = cfgDir.resolve(CONFIG_RELATIVE);
|
||||||
|
if (!Files.exists(cfgPath)) {
|
||||||
|
// 创建目录并写入模板
|
||||||
|
Files.createDirectories(cfgPath.getParent());
|
||||||
|
JsonObject tmpl = new JsonObject();
|
||||||
|
// 提供一些常见原版默认(仅作为示例,实际仍以内置 switch 为兜底)
|
||||||
|
tmpl.addProperty("minecraft:smelting", "熔炉");
|
||||||
|
tmpl.addProperty("minecraft:blasting", "高炉");
|
||||||
|
tmpl.addProperty("minecraft:smoking", "烟熏");
|
||||||
|
tmpl.addProperty("minecraft:campfire_cooking", "营火");
|
||||||
|
// GTCEu 示例占位
|
||||||
|
tmpl.addProperty("gtceu:assembler", "组装机");
|
||||||
|
tmpl.addProperty("gtceu:arc_furnace", "电弧炉");
|
||||||
|
tmpl.addProperty("gtceu:chemical_reactor", "化学反应器");
|
||||||
|
// 也支持别名(最终搜索关键字)形式,例如:
|
||||||
|
tmpl.addProperty("assembler", "组装机");
|
||||||
|
Files.writeString(cfgPath, GSON.toJson(tmpl));
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = Files.readString(cfgPath);
|
||||||
|
JsonObject obj = GSON.fromJson(json, JsonObject.class);
|
||||||
|
Map<ResourceLocation, String> map = new HashMap<>();
|
||||||
|
Map<String, String> alias = new HashMap<>();
|
||||||
|
if (obj != null) {
|
||||||
|
for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
|
||||||
|
String k = e.getKey();
|
||||||
|
JsonElement v = e.getValue();
|
||||||
|
if (v != null && v.isJsonPrimitive()) {
|
||||||
|
String name = v.getAsString();
|
||||||
|
if (name == null || name.isBlank()) continue;
|
||||||
|
if (k.contains(":")) {
|
||||||
|
// 形如 namespace:path
|
||||||
|
try {
|
||||||
|
ResourceLocation rl = new ResourceLocation(k);
|
||||||
|
map.put(rl, name);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
} else {
|
||||||
|
// 视为别名:最终搜索关键字(大小写不敏感)
|
||||||
|
alias.put(k.toLowerCase(), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CUSTOM_NAMES.clear();
|
||||||
|
CUSTOM_NAMES.putAll(map);
|
||||||
|
CUSTOM_ALIASES.clear();
|
||||||
|
CUSTOM_ALIASES.putAll(alias);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最近一次通过 JEI 填充到编码终端的“处理配方”的中文名称(如:烧炼/高炉/烟熏...)
|
||||||
|
public static volatile String lastProcessingName = null;
|
||||||
|
|
||||||
|
public static void setLastProcessingName(String name) {
|
||||||
|
lastProcessingName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向配置中新增或更新“别名 -> 中文”映射,并刷新内存映射。
|
||||||
|
* 仅用于非原版(或希望使用最终搜索关键字)场景。
|
||||||
|
*
|
||||||
|
* @param aliasKey 最终搜索关键字(不含冒号),大小写不敏感
|
||||||
|
* @param cnValue 中文名称
|
||||||
|
* @return 是否写入成功
|
||||||
|
*/
|
||||||
|
public static synchronized boolean addOrUpdateAliasMapping(String aliasKey, String cnValue) {
|
||||||
|
if (aliasKey == null || aliasKey.isBlank() || cnValue == null || cnValue.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path cfgDir = FMLPaths.CONFIGDIR.get();
|
||||||
|
Path cfgPath = cfgDir.resolve(CONFIG_RELATIVE);
|
||||||
|
if (!Files.exists(cfgPath)) {
|
||||||
|
// 若文件不存在,先创建模板
|
||||||
|
loadRecipeTypeNames();
|
||||||
|
}
|
||||||
|
JsonObject obj;
|
||||||
|
if (Files.exists(cfgPath)) {
|
||||||
|
String json = Files.readString(cfgPath);
|
||||||
|
obj = GSON.fromJson(json, JsonObject.class);
|
||||||
|
if (obj == null) obj = new JsonObject();
|
||||||
|
} else {
|
||||||
|
obj = new JsonObject();
|
||||||
|
}
|
||||||
|
String key = aliasKey.trim();
|
||||||
|
// 仅允许作为别名写入(不含冒号),如包含冒号,仍按原样写入,但推荐别名
|
||||||
|
obj.addProperty(key, cnValue);
|
||||||
|
Files.createDirectories(cfgPath.getParent());
|
||||||
|
Files.writeString(cfgPath, GSON.toJson(obj));
|
||||||
|
|
||||||
|
// 更新内存映射
|
||||||
|
if (key.contains(":")) {
|
||||||
|
try {
|
||||||
|
ResourceLocation rl = new ResourceLocation(key);
|
||||||
|
CUSTOM_NAMES.put(rl, cnValue);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
} else {
|
||||||
|
CUSTOM_ALIASES.put(key.toLowerCase(), cnValue);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
sendPlayerMessage(Component.literal("ExtendedAE_Plus: 配置文件解析失败, " + e.getMessage()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按中文值精确匹配删除映射(支持别名与完整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<String> toRemove = new java.util.ArrayList<>();
|
||||||
|
for (java.util.Map.Entry<String, JsonElement> 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 (JsonSyntaxException e) {
|
||||||
|
sendPlayerMessage(Component.literal("ExtendedAE_Plus: 配置文件解析失败, " + e.getMessage()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 供搜索使用的关键字映射:
|
||||||
|
* - 有中文映射则返回中文;
|
||||||
|
* - 否则返回配方类型的 path(不含命名空间),例如 assembler。
|
||||||
|
*/
|
||||||
|
public static String mapRecipeTypeToSearchKey(Recipe<?> recipe) {
|
||||||
|
if (recipe == null) return null;
|
||||||
|
RecipeType<?> type = recipe.getType();
|
||||||
|
ResourceLocation key = BuiltInRegistries.RECIPE_TYPE.getKey(type);
|
||||||
|
if (key == null) return null;
|
||||||
|
// 先查别名(按 path 匹配)
|
||||||
|
String alias = CUSTOM_ALIASES.get(key.getPath().toLowerCase());
|
||||||
|
if (alias != null && !alias.isBlank()) return alias;
|
||||||
|
// 再查完整ID映射
|
||||||
|
String custom = CUSTOM_NAMES.get(key);
|
||||||
|
if (custom != null && !custom.isBlank()) {
|
||||||
|
return custom;
|
||||||
|
}
|
||||||
|
return key.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅使用反射的 GTCEu GTRecipe -> 搜索关键字(避免在运行时直接引用 GTCEu 类)。
|
||||||
|
*/
|
||||||
|
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"
|
||||||
|
*/
|
||||||
|
public static String deriveSearchKeyFromUnknownRecipe(Object recipeBase) {
|
||||||
|
if (recipeBase == null) return null;
|
||||||
|
try {
|
||||||
|
Class<?> cls = recipeBase.getClass();
|
||||||
|
String simple = cls.getSimpleName();
|
||||||
|
String pkg = cls.getName();
|
||||||
|
|
||||||
|
String ns = null;
|
||||||
|
String lower = pkg.toLowerCase();
|
||||||
|
if (lower.contains("gtceu")) ns = "gtceu";
|
||||||
|
else if (lower.contains("gregtech")) ns = "gregtech";
|
||||||
|
else if (lower.contains("projecte")) ns = "projecte";
|
||||||
|
else if (lower.contains("create")) ns = "create";
|
||||||
|
else if (lower.contains("immersiveengineering")) ns = "immersive";
|
||||||
|
|
||||||
|
String token = toSearchToken(simple);
|
||||||
|
String key;
|
||||||
|
if (ns != null && token != null && !token.isBlank()) key = ns + " " + token;
|
||||||
|
else key = token != null && !token.isBlank() ? token : ns;
|
||||||
|
if (key == null || key.isBlank()) return null;
|
||||||
|
// 尝试别名映射(大小写不敏感)
|
||||||
|
String alias = CUSTOM_ALIASES.get(key.toLowerCase());
|
||||||
|
return (alias != null && !alias.isBlank()) ? alias : key;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toSearchToken(String simpleName) {
|
||||||
|
if (simpleName == null || simpleName.isBlank()) return null;
|
||||||
|
// 去掉常见后缀
|
||||||
|
String s = simpleName
|
||||||
|
.replaceAll("Recipe$", "")
|
||||||
|
.replaceAll("Recipes$", "")
|
||||||
|
.replaceAll("Category$", "")
|
||||||
|
.replaceAll("JEI$", "");
|
||||||
|
// 驼峰转空格并小写
|
||||||
|
s = s.replaceAll("(?<!^)([A-Z])", " $1").toLowerCase();
|
||||||
|
// 取首个关键词
|
||||||
|
s = s.trim();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user