Files
90CNServer/pvf/docs/标注功能设计.md
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

289 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
设计一个类似标注的功能,核心目的是解决“哪个配置配什么东西”的问题:
- 标注走规则配置,而非针对具体内容的单独标注
- 标注内容包括 title/content
- 标注在编辑器内的显示:
- 在被标注内容旁边直接加上一个 tag 显示标题,hover tag 显示 tooltip
- 复制内容的时候不要复制标注
- 标注规则:
- 可指定生效的文件后缀
- 如:.equ 文件下的 [rarity],标注为“装备品级”
- 如果不指定后缀,则全局生效
- 可对 section 本身进行标注
- 可对 section 内的数据进行标注
- 指定 section 内第几个 token(或者范围)
- 标注类型
- 枚举,如稀有度的 3-神器、4-史诗
- 关联 id,可指定关联的类型,标注显示其 id 对应的 name,且可通过 cmd/ctrl + 单击跳转到对应文件
- 可对路径,包括目录和文件进行标注,此标注显示在现有文件树的 name tag 位(一般不会对有 name 的做标注,但设计上可以共存)
- 标注规则第一版用 json 文件定义,设计好 json 结构。
## 已确认的产品约束
- 第一版规则为系统内置规则,不在应用内提供用户编辑入口。
- 规则通过单独的 HTML 可视化编辑器维护,由 Go 本地服务提供页面、校验、预览和实时写盘能力。
- 配置统一放在根目录 `config/`:`annotations.json` 保存手写规则,`paths.json` 保存批量目录/文件树注释,`sections.json` 保存批量 section 注释,`lists.json` 保存固定的 lst/关联索引。HTML 编辑器只写入 `config/annotations.json`,写入前将旧内容保存为 `annotations.json.bak`。
- 规则和列表配置通过 `go:embed` 提供启动默认值;应用工具栏提供手动重载按钮,可在运行中校验并切换到磁盘上的最新配置。
- section 名称重复时,规则匹配全部同名 section,不配置 occurrence。
- Tag 的显示位置通过设置面板切换,设置面板在标注功能之前完成。
- 多条规则命中同一位置时,Tag 只显示规则列表中的第一条,Tooltip 显示全部命中规则。
- 引用 ID 的 Cmd/Ctrl + 单击只负责打开目标文件,不要求定位到目标 section 或 token。
## 可行性结论
整体可行性较高,但这是一个跨越 PVF 解析、规则匹配、编辑器渲染和文件跳转的功能,不是简单的文本后缀处理。
当前 PVF 脚本由固定 5 字节 token 流组成,section 和 token 类型已经明确,适合实现结构化匹配。现有反编译器只返回纯文本,没有保留 token 对应的文本范围;CodeMirror 目前也只接收纯文本文档。因此最关键的工作是建立“语义 token 到编辑器文本范围”的映射。
文件树已经有 Tag 展示能力,现有搜索索引也具备 ID、名称和路径关联的基础。路径标注可以较低成本复用这部分能力;section/token 标注和引用跳转需要新增通用的标注引擎。
## 推荐总体方案
### 1. 内置规则
建议使用以下结构:
```text
config/annotations.json
config/paths.json
config/sections.json
config/lists.json
internal/annotations/loader.go
tools/annotation-editor.html
```
Go 通过 `go:embed` 加载这些配置文件,应用启动时完成规则和列表配置解析、合并与校验。配置文件不写入 PVF。
`config/paths.json` 存放批量生成的 `target.kind: "path"` 规则(目录树与文件树注释),glob 都是精确路径:目录注释只描述该目录本身,不向子孙继承,因此没有自己规则的路径不显示标注。加载器把它追加在 `annotations.json` 的规则之后,因此手写规则仍决定同一路径的首个标题;两个文件出现相同 `id` 时保留 `annotations.json` 中的那条并跳过重复项。它由外部数据整理生成,不由 HTML 编辑器维护。手写规则仍可使用 `/**` 递归 glob,批量 path 规则在编译期按字面 glob 和 `<dir>/**` 前缀建索引,避免每个树节点都全量扫描规则。
`config/sections.json` 存放批量生成的 `target.kind: "section"` 规则,来自 pvfUtility `PvfComments`(按扩展名限定,title 为中文注释名,content 保留原始说明与官方示例;只保留能在实际 PVF 数据中确认存在的段)。合并与去重规则同上,手写 section 规则仍决定同名段的首个标题。
HTML 编辑器负责规则列表管理、规则排序、表单编辑、JSON 导入导出、错误展示和示例预览。它不直接依赖浏览器文件系统权限,而是由 `cmd/annotation-editor` 启动仅监听 loopback 的 Go HTTP 服务,并自动打开系统浏览器。
启动命令为:
```text
task annotation:editor
```
服务监听 `127.0.0.1:0`,使用系统分配的随机端口,并为 API 请求附加当前会话令牌。提供以下接口:
- `GET /api/rules`:读取并校验当前规则(只含 `annotations.json`,不含批量 `paths.json` / `sections.json`),同时提供只读的列表关联供规则选择。
- `PUT /api/rules`:只校验并写入 `annotations.json`;通过后创建 `.bak`,再使用临时文件和 rename 原子替换源文件,`lists.json`、`paths.json` 与 `sections.json` 不由规则编辑器修改。
- `POST /api/preview`:使用提交的当前规则、示例路径和反编译文本调用 Go 规则引擎,返回编辑器标注和路径标注。
表单的有效修改会在短暂防抖后实时保存。校验失败时展示具体错误,且不能覆盖现有 JSON。导入和导出继续保留,便于批量维护和迁移规则。
每次有效保存还会同步运行时规则和列表配置到 `os.UserConfigDir()/pvfine/annotations.json`、`lists.json`。应用内“重载标注规则”按钮在开发环境优先读取仓库源文件,发布环境读取运行时副本;`paths.json`、`sections.json` 取同一目录下的同名文件,不存在时回退到内置数据。重载会先完整校验这些文件;失败时保留当前规则引擎,成功后重建路径标注、清空 relation/编辑器标注缓存、搜索索引,并刷新已打开文件和当前文件树。
规则文件中应保留 `version` 和可选的 `description` 字段,便于后续升级格式和说明规则用途。
### 2. JSON 结构
建议第一版只使用扩展名匹配、路径 glob 匹配和精确 section 名称匹配,不引入正则表达式。
```json
{
"version": 1,
"rules": [
{
"id": "equ.rarity",
"match": {
"extensions": [".equ"]
},
"target": {
"kind": "token",
"section": "rarity",
"index": 0
},
"annotation": {
"title": "装备品级",
"type": "enum",
"values": {
"3": "神器",
"4": "史诗"
}
}
},
{
"id": "equ.related-equipment",
"match": {
"extensions": [".equ"]
},
"target": {
"kind": "token",
"section": "related",
"index": 0
},
"annotation": {
"title": "关联装备",
"type": "reference",
"relation": "equipment"
}
},
{
"id": "path.equipment",
"match": {
"glob": "equipment/**/*.equ"
},
"target": {
"kind": "path"
},
"annotation": {
"title": "装备配置",
"type": "text",
"content": "装备相关配置文件"
}
}
]
}
```
固定列表和关联索引单独放在 `config/lists.json`:
```json
{
"version": 1,
"relations": {
"装备": {
"listPath": "equipment/equipment.lst",
"idToken": 0,
"pathToken": 1,
"recordTokens": 2,
"nameSection": "name"
}
}
}
```
结构约定如下:
- `target.kind` 支持 `path`、`section`、`token`。
- `section` 目标匹配所有同名 section。
- token 下标使用 0-based;范围使用 `start` 和 `endExclusive`。
- token 目标可以配置 `recordTokens`,按固定数量的 token 重复匹配每条记录;`offset` 会跳过 section 开头的若干 token,再开始重复匹配;`tokensPerLineIndex` 指定 section 内直接 token 的 0-based 下标,其正整数值会覆盖 `recordTokens`,无效或缺失时回退到 `recordTokens`;`contextIndex` 可覆盖关联类型指定的上下文 token 下标。
- 默认只计算 section 的直接 token,不递归计算嵌套 section。
- `nameSection`(relation 取名)、itemshop 的 `[NPC]` 取值,以及 `[name]` 名字索引只认一级 section;嵌套 section 里的同名 `[name]` 属于子记录,不会被当作文件自身的名字。
- `annotation.type` 支持 `text`、`image`、`enum`、`reference`、`path`。
- `path` 用单个 token 的路径值查找归档内文件;`annotation.pathRoot` 可填写归档内相对目录作为前缀,留空时从归档根目录查找。路径中的反斜杠会按正斜杠处理;找到目标后可 Cmd/Ctrl+单击跳转。
- `annotation.title` 可以留空;留空时不显示标注 Tag,已有的正文标注交互(例如路径跳转)仍然可用。
- enum 使用 token 的规范化值作为 key。
- reference 通过 `config/lists.json` 中的 relation 类型解析目标文件、目标 ID 和 name。
- `kind: "union"` 的 relation 可以组合多个固定列表;默认提供 `物品`,按 `装备`、`道具` 的顺序查找,适合 ID 可混用的字段。
- 普通 relation 使用固定 `listPath`;`kind: "contextual"` 的 relation 使用 `contextToken` 指定的同一条记录上下文值,从 `contextPaths` 选择对应的 lst,再用目标 ID 查找技能文件。例如 `[skill levelup]` 每条记录有 3 个 token,目标为 `index: 1`、`recordTokens: 3`,关联类型可把 `[fighter]` 映射到 `skill/fighterskill.lst`。
- 规则数组顺序就是优先级。
### 3. 编辑器标注模型
后端解析当前文件时,同时返回编辑器文本和标注范围:
```text
text
annotations: [
{
start,
end,
title,
content,
type,
targetFileIndex
}
]
```
`start/end` 使用 JavaScript UTF-16 code unit 偏移,避免中文和代理对字符导致 CodeMirror 定位错误;CRLF 和 CR 按 CodeMirror 的规则各占一个换行单元。
标注不能写入编辑器文档。前端使用 CodeMirror Decoration/Widget 在 token 或 section 旁边渲染 Tag,Tooltip 使用聚合后的全部命中规则。由于 Widget 不属于 `state.doc`,复制内容时不会复制标注;复制行为仍需要通过前端测试验证。
同一文件、同一 section/token 范围的多条命中应聚合成一个 anchor:
- 可见 Tag 使用第一条规则的 title。
- Tooltip 按规则顺序显示全部 title/content。
- 引用 Tag 携带 `targetFileIndex`,Cmd/Ctrl + 单击调用现有文件打开流程。
编辑文本后只重新计算当前文件的标注,不能通过整体替换 CodeMirror 文档刷新,否则可能导致光标和选择范围跳动。
### 4. 文件树标注
路径标注需要同时支持目录和文件。当前文件树标签主要按文件索引保存,应扩展为按归档路径保存,例如:
```go
map[string][]AnnotationTag
```
`ListChildren` 返回目录或文件节点时,根据完整路径附加标注。文件原有的 name、ID 关联标签与路径标注可以同时存在。
### 5. 显示设置
Tag 的显示位置属于前端设置,不放入规则 JSON。设置面板完成后建议预留以下设置:
```text
annotation.tagPlacement:
after-target
line-end
hidden
```
显示设置只影响 Tag 的渲染位置和可见性,不影响 Tooltip 中的完整标注内容。
设置通过 Wails `SettingsService` 读写,不由前端直接操作文件。设置文件位于:
```text
os.UserConfigDir()/pvfine/settings.json
```
默认值为 `after-target`,保存时同样使用临时文件和 rename 原子替换。第一版设置面板只提供标注显示位置,保留后续扩展通用设置的结构。
## 性能策略
- 路径标注在建立目录索引时计算。
- 搜索索引从 `relations` 中读取固定 `listPath` 和 contextual `contextPaths`,装备、道具、技能等列表不再在索引器内重复配置。
- section/token 标注只在打开文件或编辑当前文件时计算,不在打开归档时解析所有 `.equ` 文件。
- reference 索引按 relation 类型懒加载并缓存。
- 当前文件标注按文件索引和精确编辑器文本缓存;切换归档或修改文本时失效。
- 对不支持的 `TypeUnicode` 文件,第一版只提供路径标注;section/token 标注先限定为 `TypeScript`。
## 实施阶段
1. 增加内置 JSON 加载、版本校验和规则错误提示。
2. 实现独立 HTML 规则编辑器和 JSON 导入导出。
3. 重构 PVF 反编译流程,同时输出 token/section 位置。
4. 实现路径标注和文件树 Tag。
5. 实现编辑器 Tag、Tooltip、复制排除和 Tag 显示设置。
6. 实现 enum 标注、规则冲突聚合和编辑后刷新。
7. 实现 reference relation、name 解析和 Cmd/Ctrl 点击打开文件。
## 主要测试
- JSON 版本、字段和规则错误校验。
- 扩展名、路径 glob、section 和 token 范围匹配。
- 重复 section 全部命中。
- 中文、反引号、嵌套 section 和不同 token 类型的范围偏移。
- 多条规则命中时只显示第一条 Tag、Tooltip 显示全部内容。
- 编辑文本后标注刷新且光标不跳动。
- 复制文本不包含 Tag。
- reference ID 不存在、重复和跨类型时的降级行为。
- 目录和文件路径标注同时存在时的文件树显示。
## 适用 PVF 版本
规则 `rules[]` 和共享字段 `fields[]` 均支持可选的 `pvfVersions` 数组:
```json
{
"id": "equ.rarity",
"pvfVersions": ["90US", "110US"],
"match": {"extensions": [".equ"]},
"target": {"kind": "token", "section": "rarity", "index": 0},
"annotation": {"title": "装备品级", "type": "enum", "values": {"4": "史诗"}}
}
```
- 只接受 `90US`、`90CN`、`110US`、`115US`;省略属性或空数组表示全版本可用,旧配置无需迁移,顶层 `version` 仍为 `1`。`110US` 和 `115US` 独立匹配,共用规则须显式列出两个版本或不限定版本。
- 当前已知格式映射为 `standard → 90US`、`alternate → 90CN`、`paged110 → 110US`、`paged115 → 115US`。`paged`(未登记客户端的分页容器)、`recovered` 及其他未知格式只启用未限定版本的配置。映射用于配置适用性,不决定解析或写入能力。
- 规则引用共享字段时,两处版本限制同时生效。交集为空时规则不生效;字段自身若适用,仍可生成隐式标注。先筛选,再判断显式规则是否覆盖字段。
- 共享字段的版本限制同时影响标注和结构化预览;关联列表配置不受影响。
- 后端保留完整规则文档,按当前归档建立运行视图,统一作用于编辑器、目录树和字段预览。切换归档、恢复状态及热重载时更新运行视图与相关缓存。
- 配置编辑器表单末尾提供版本复选框;试运行区域提供四个版本及“未知版本”单选,默认 `90US`。`POST /api/preview` 可传 `pvfVersion`,省略或空字符串表示未知版本,非法值返回 HTTP 400。