把钥匙和钥匙卡按所属地图归类:藏身处/跳蚤市场(手册)里的 钥匙 下面会多出 12 个地图子分类,
钥匙名字前面自动加上 【地图名】,描述顶部加一行地图标识,跳蚤市场的分类树也会同步跟着变。
参考 MGmod(麻瓜编辑器)的钥匙分类思路重写,独立服务端 mod(net10.0)。
兼容性:按 SPT 4.1.0 的公开 API 编译,4.1.0 – 4.1.x 的服务端都能直接加载(.NET 会把程序集引用往前滚动到更新版本)。 1.0.0 是用 4.1.6 编译的,所以在 4.1.5 上会报
built for a different version of SPT,请用 1.0.1 及以后。
手册 / 跳蚤市场
└─ 钥匙
├─ 海关 (32 把)
├─ 工厂 (2 把)
├─ 森林 (5 把)
├─ 立交桥 (15 把)
├─ 海岸线 (48 把)
├─ 储备站 (31 把)
├─ 实验室 (12 把)
├─ 灯塔 (17 把)
├─ 塔科夫街区 (39 把)
├─ 中心区 (5 把)
├─ 迷宫 (10 把)
└─ 未知 (12 把,即不绑定具体地图的钥匙)
物品名变成 【海关】公司主管办公室钥匙,描述第一行是 海关(带颜色)。
语言跟随客户端:中文客户端显示中文,俄语客户端显示俄语……共支持游戏自带的 17 种语言,
地图名直接取游戏自己的地图名翻译,不需要额外维护词条。
原来的 机械钥匙 / 电子钥匙 两个分类在搬空后会自动删除(可关闭)。
keymaps.json 里的地图归属不是猜的,来源和核对过程如下:
- 底表取 MGmod 自己的
MapKey.json(12 张图共 241 条,含 1 条重复),只保留当前版本 手册里真实存在、并且本来就在机械钥匙/电子钥匙下的 228 条——这正好是游戏"钥匙"分类的全部 214 + 14 把,一把不多一把不少。 - MG 把它标记成"未知"的钥匙里,有 5 把按证据挪进了具体地图:
机械钥匙(海关,任务/跳蚤标记)、RB-PP钥匙(储备站,描述写明"联邦储备基地防空地堡")、Horse饭店厕所钥匙(中心区,描述写明 "市中心 Horse 饭店")、迷宫钥匙与行刑室钥匙(迷宫,描述写明迷宫/行刑室)。 - 剩下 12 把留在"未知":折叠车钥匙、VAZ 车钥匙、占位的"钥匙 2/3/5"、Black Keycard、 泵站前后门、武器保险柜、手枪收纳箱、Missam 叉车钥匙——描述里本身就没有地图信息,MG 也归在未知。
- 拿 tarkov.help 的地图标记(每把钥匙"在哪个图开什么")做独立交叉核对:228 把 tpl 完全一一对应;
有地图标记的 189 把里 188 把一致。唯一不一致的是
Labrys 访问钥匙卡:描述说它开"Knossos 地下 设施"(迷宫那套米诺陶主题),MG 也放在迷宫,但 tarkov.help 把它标在海岸线的电子门上;本 mod 按 MG/描述放在迷宫,想改的话在 keymaps.json 里把它从labyrinth挪到shoreline即可。 - 其余 39 把 tarkov.help 没有地图标记,逐条按名字/描述核对过(疗养院各房间、RB-*、Concordia 公寓、 中心区 Horse/Unity Credit、迷宫各房间等),没有发现明显错放。
- MG 的
MapKey.json里还有 13 条本 mod 不处理,原因分别是:7 条不在当前手册里(已删除的旧物)、 4 条在"任务物品"分类(备用藏身处钥匙/任务测试钥匙/疗养院封闭房间的钥匙/塔科夫纪念品钥匙)、 2 条是"特殊装备"里的BBQ-S43 喷枪和阀门手轮(名字像钥匙,其实是工具)。
- 把
KeyClassify整个文件夹放进<SPT目录>/user/mods/ - 启动服务端,日志里出现这行就说明生效了:
[KeyClassify] Classified 228 key(s) into 12 map categories (12 created, 0 refreshed, 228 re-parented, 2 emptied category/categories removed).
只改服务端数据,不需要装客户端插件;卸载的话删掉文件夹、重启服务端即可。 已经装过的旧存档不受影响,键位、跳蚤价格、任务条件都不变,变的只是"手册里挂在哪个分类下"和显示名。
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
总开关,关掉就完全不动手册 |
nameFormat |
【{map}】{name} |
物品名格式,{map} 本地化地图名,{name} 原名(必须有) |
addDescriptionHeader |
true |
是否在描述顶部插入地图标识 |
descriptionHeader |
<color=#00cccc><b>{map}</b></color> |
描述顶部那行的格式 |
includeKeycards |
true |
false 时钥匙卡保持原样,只有普通钥匙按地图分类 |
createUnknownCategory |
true |
false 时不生成"未知"分类,这批钥匙留在原分类 |
removeEmptiedOriginalCategories |
true |
搬空后是否删掉原来的"机械钥匙 / 电子钥匙" |
parentCategoryId |
5b47574386f77428ca22b342 |
地图分类挂在哪里(默认是"钥匙") |
mechanicalCategoryId / electronicCategoryId |
5c518ec9… / 5c518ed5… |
原分类 id,用来判断钥匙卡、以及判断该不该删除 |
categoryColor |
"" |
分类颜色,留空表示不上色 |
想让名字更朴素,把 nameFormat 改成 [{map}] {name} 之类的即可;改完重启服务端。
真正决定"哪把钥匙属于哪张地图"的地方。一个地图一个对象:
{
"id": "customs",
"categoryId": "6f646d000000000000000001",
"order": "101",
"icon": "/files/handbook/icon_keys_mechanic.png",
"localeKeys": ["Customs", "bigmap"],
"names": { "ch": "海关" },
"keys": ["6761a6f90575f25e020816a4", "..."]
}id:内部标识,只出现在日志里categoryId:手册分类 id,必须是 24 位十六进制,一旦有人玩过存档就别再改(它就是这个分类的身份证)order:排序,数字字符串;游戏里顶级分类用"0".."13"、子分类用"100",所以101起排在默认项后面icon:分类图标,留空用/files/handbook/icon_keys_mechanic.pnglocaleKeys:去游戏语言表里查地图名的键,按顺序取第一个命中的names:按语言硬覆盖地图名(优先级最高),不写也行keys:属于这张地图的物品 tpl 列表
加新钥匙 / 改归属:把 tpl 从一张地图的 keys 数组挪到另一张即可;游戏更新加了新钥匙,补进对应数组就行,不用改代码。
加新地图(新 DLC 图等):复制一个对象,换 id、新的 categoryId(自己编 24 位十六进制,别重复)、localeKeys 写新图在语言表里的名字键。
- SPT 的手册数据是
TemplateTable.Handbook(Categories+Items),本 mod 在OnLoadOrder.PostLoad + 50执行,也就是最后改手册,读完配置后:建地图分类 → 把物品的ParentId指到地图分类 → 删掉搬空的原分类。 - 跳蚤市场不需要另外处理:客户端跳蚤分类树就是手册树;服务端
RagfairHelper.GetCategoryList也是按 手册分类 id 找物品(TemplatesWithParent/ChildrenCategories),所以手册里分了组,跳蚤里自然就分开了。 - 名字和描述走语言表:给每种语言的
LazyLoad<GlobalLocaleDictionary>挂一个 transformer, 在该语言第一次被请求时改写"<tpl> Name"/"<tpl> Description",并写入分类名"<categoryId>"。 这样做的好处是:地图名用的是"该语言自己的"翻译,中文客户端不会看到英文图名。 PostLoad + 50排在 ItemInfo 之类会改名字的 mod 之后,因此它们加的稀有度染色、★之类会留在内层, 我们的【地图】前缀在最外层。- 还有一步容易被忽略:SPT 在第一次有人查手册价格/分类时,会把手册复制成一份内部索引
(
HandbookHelper里的_handbookPriceCache)并一直用到重启。启动时别的 mod 往往已经先读过手册价格, 这份索引里就还是"机械钥匙/电子钥匙"。所以本 mod 改完手册后会把这个缓存清空 (HandbookCacheRefresher,反射置空),下次访问自动按新手册重建——否则跳蚤里点新分类会是空的。 日志里出现Dropped SPT's cached handbook lookup ...就是在做这件事。
需要 .NET 10 SDK。编译只依赖 NuGet 上的 SPT 程序集(SPTarkov.Server.Core / SPTarkov.Common / SPTarkov.DI 的 4.1.0 版),
不需要本机装 SPT;只有"构建完顺手拷进游戏目录"这一步才用到 SPT 安装路径(默认 E:\GAME\SPT_Runtime)。
dotnet build -c Release # 产物在 artifacts/ 下,可直接打包
dotnet build -c Debug # 编译并自动拷到 <SptServerDir>/user/mods/KeyClassify
dotnet build -c Debug -p:SptServerDir="D:\SPT" # 指定别的安装位置
dotnet build -c Debug -p:SkipModCopy=true # 只编译,不碰游戏目录
dotnet build -c Release -p:MinSptVersion=4.1.2 # 只支持 4.1.2 及以后(会写进 dll 的程序集引用)
为什么锁 4.1.0:dll 里记录的是编译时引用的程序集版本(SPTarkov.Server.Core, Version=x.y.z.0)。
.NET 只会把引用"往前"滚动到更新的版本,不会往回滚,所以用 4.1.6 编出来的 dll 在 4.1.5 上会因找不到 4.1.6
程序集而被 SPT 的加载器拒掉;反过来用 4.1.0 编的 dll 在 4.1.6 上正常加载(SPT 只拦"引用了比服务端更新的版本"的 mod)。
- 修复在 SPT 4.1.5 上加载失败:
Mod KeyClassify was built for a different version of SPT and was not loaded. Reason: Could not load file or assembly 'SPTarkov.Server.Core, Version=4.1.6.0'。 - 改为按 NuGet 上 SPT 4.1.0 的程序集编译,4.1.0 – 4.1.x 全系列通用,不再需要按服务端版本挑构建。
- 首个版本:钥匙 / 钥匙卡按地图分成 12 个子分类,名字加
【地图】前缀,描述顶部加地图标识, 跳蚤市场分类树同步;支持 17 种语言、可选关闭钥匙卡分类、自动删除搬空的原分类。
- 名字/分类没变? 确认服务端日志有
[KeyClassify]那行;没有就是 mod 没加载(看user/logs/)。 客户端如果开着,重启客户端重新拉手册。 - 和别的 mod 冲突? 本 mod 只改手册的
ParentId、语言表的名字/描述;如果另一个 mod 也搬钥匙, 后加载的赢。把本 mod 的PostLoad + 50调大/调小(Plugin.cs)可以改变先后。 - 想恢复原样? 把
enabled设成false(或删掉 mod),重启服务端。手册是每次启动从SPT_Data/database/templates/handbook.json重新读的,不会留下永久修改。 - 跳蚤里地图分类是空的? 说明手册索引没被刷新(旧版本的问题,本版本已修)。看日志有没有
Dropped SPT's cached handbook lookup;如果提示SPT changed how it caches the handbook, 那是 SPT 改了内部字段名,HandbookCacheRefresher.cs里的_handbookPriceCache需要跟着改。 - 钥匙卡想单独放?
includeKeycards: false,钥匙卡会留在"电子钥匙"分类里。