实体精华配置文件

基本信息

属性 值
配置目录 config/ThaumcraftMobAspects/(常量 ModFileCache.CONFIG_FOLDER)
文件粒度 每个 modid 一个文件 <modid>.cfg
文件格式 Gson pretty-printed JSON 数组(不是 Forge cfg 语法)
读取类型 ThaumcraftEntity[](源码注释:数组是必需的,Gson 处理 MultiMapSet 有问题)
容器 HashMultimap<String, ThaumcraftEntity>,键为 modid

这些 .cfg 文件虽然用 Forge 的扩展名,里面装的却是 JSON 文本。

postInit 的处理顺序(决定优先级)

post() 里的步骤顺序不可交换,因为同名去重是"先到先得":

步骤 动作 结果
1 遍历 getModIds(),readFromFile 读入全部已有配置 最先写入容器
2 put 两个玩家名默认条目 同名时第 1 步的胜出
3 6 个插件的 getThaumcraftMobs() 逐个 put 同名时配置值覆盖硬编码值
4 遍历 EntityList 所有实体名,put 空精华条目 同名时被前面的胜出
5 遍历容器,saveToFile 写回每个 modid 的文件 落盘
6 遍历容器,registerEntityTag 空列表跳过

所以玩家改配置文件能覆盖插件的硬编码值——这是本 mod 唯一的定制入口。 反过来,改了插件源码也不会覆盖玩家已有的配置(除非玩家先删文件)。

第 4 步的注释是 // Add Generic Entities that have no values: 它为所有已注册实体(包括别的 mod 的)建立空条目, 目的其实是把每个实体都写进它所属 modid 的文件里,方便玩家在其中添加精华。 这些空条目在第 6 步因 aspectList.size() > 0 而被跳过,不注册任何东西。

modid 的推断:guessModId

配置文件的命名完全依赖 guessModId(String entityName) 的四级回退 (ModFileCache.java:67-103):

级别 条件 返回
1 实体名含点 entityName.split("\\.")[0]
2 扫 EntityList.classToStringMapping,找同包且注册名含点的项 该注册名的第一段
3 命中 entityPackageToPrefix 手工表 vanilla / vanillaitem / unknown
4 兜底 包名第一段;包名无点则 unknown

手工表在构造器里预置 5 条(ModFileCache.java:28-32):

包名前缀(去掉了最后一段类名) modid
net.minecraft.entity.boss vanilla
net.minecraft.entity.monster vanilla
net.minecraft.entity.passive vanilla
net.minecraft.entity.item vanillaitem
net.minecraft.entity.player PLAYER_CATEGORY

第 1 级只看第一个点之前的部分,所以 "TwilightForest.Block&Chain Goblin" → TwilightForest(空格和 & 在第一个点之后,不影响)。 第 2 级刻意排除原版(要求注册名含点),避免原版实体被误判成某个 mod。

文件名的合法字符过滤

filterModIdToValidCharacters(ModFileCache.java:52-54)用 CharMatcher.anyOf("qwertyuiopasdfghjklzxcvbnm0QWERTYUIOPASDFGHJKLZXCVBNM123456789").retainFrom(modId) ——字符集合同时包含大小写字母与数字,只剔除其余字符。

实际影响:

  • BiomesOPlenty、MoCreatures 的大小写被保留(集合含两种大小写字母), 文件名仍是 BiomesOPlenty.cfg;
  • 若 modid 含 _、.、-,这些字符会被静默删除, 如 some_mod → somemod.cfg;
  • 该过滤只作用于文件名,实体名本身不受影响。

getFile() 惰性创建目录与空文件(getParentFile().mkdirs() + createNewFile()), 所以即使某 mod 一个实体都没有,也会留下一个空的 [] 文件。

源码缺陷

UNKNOWN_CATEGORY 与 PLAYER_CATEGORY 是同一个字符串。 ModFileCache.java:19-20:

public static final String UNKNOWN_CATEGORY = "unknown";
public static final String PLAYER_CATEGORY = "unknown";

两个常量值都是 "unknown",显然是复制粘贴时漏改。后果是 玩家条目与无法归属的实体被混写进同一个 unknown.cfg, 玩家无法通过文件名区分"这俩是我加的"还是"这俩是兜底进来的"。 the_iguana_man / XanderGryphon 就在这个文件里。

每条记录都带 3 个冗余字段。 ThaumcraftEntity 的 _HEADER、_FOOTER、_COMMENT 是实例字段(public final), 不是 static。Gson 按实例字段序列化,因此 JSON 数组里每一条记录都会重复输出:

"_HEADER": "*****************************************",
"_FOOTER": "*****************************************",
"_COMMENT": "Aspects are in the form aspect : value ",

这些常量从未被读取,只是无害的体积浪费(O(实体数) × ~130 字符)。

IguanaLog 定义了但全仓库零引用。 IguanaLog 类提供 log 字段, grep -rn "IguanaLog" src/ 除自身文件外无任何命中。 实际错误处理全部走 e.printStackTrace()(ModFileCache.java:47、 ThaumcraftMobAspects.java:157、:168),不写进 Minecraft 日志文件。

dependencies.gradle 声明了未被使用的运行时依赖。 其中 runtimeOnly("com.github.GTNewHorizons:Baubles:1.0.4:dev"), 但 grep -rin "bauble" src/main/java 无任何命中——源码完全不涉及 Baubles。 @Mod 的 dependencies 串里也没有 Baubles,所以它不会被强制加载。

ModFileCache.java:24 有重复分号:new HashMap<String, File>();; 语法合法、无功能影响,属遗留笔误。

相关条目