DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,264 @@
|
||||
# 支援兵 AIC 覆盖(companionpvf)使用说明
|
||||
|
||||
本文件说明如何重新生成、校验、启用支援兵的 **PVF AIC 覆盖**。
|
||||
覆盖的作用:把服务端编译出的 AI 程序真正写进 `aicharacter/**.aic`,
|
||||
让支援兵的行为与 GM 工具里的 AI 档位一致;只调 GM 滑块而不做覆盖时,
|
||||
支援兵跑的是客户端 PVF 里的**原版 AIC**。
|
||||
|
||||
## 一、工具位置
|
||||
|
||||
| 项 | 路径 |
|
||||
|---|---|
|
||||
| 源码 | `go-server/cmd/companionpvf/` |
|
||||
| 可执行 | `runtime/bin/companionpvf.exe` |
|
||||
| 暂存目录 | `runtime/companion-overlay/` |
|
||||
|
||||
移植时补上的两处基础设施(原树没有):
|
||||
|
||||
- `internal/platform/pvf/ensure_string.go`
|
||||
- `Archive.EnsureANSIString` / `Archive.EnsureUnicodeString`
|
||||
- `Archive.RepackRawAndStringPools` ← **关键**:`RepackRaw` 只重写文件体、
|
||||
不写回被追加过的字符串池,补丁引用会解析成空串;本方法会用当前
|
||||
`strA/strW` 重新编码 name 段。
|
||||
- `Archive.ansiOffsets` 字段(ANSI 池偏移缓存)
|
||||
|
||||
## 二、生成
|
||||
|
||||
`profiles.json`(**必须无 BOM**,否则 `invalid character '\ufeff'`):
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"behavior": "aggressive",
|
||||
"class_id": 0,
|
||||
"buff_policy": "leader_first",
|
||||
"combo_preset": "auto",
|
||||
"combo_enabled": true,
|
||||
"aoe_min_targets": 3,
|
||||
"protect_leader": true
|
||||
}
|
||||
],
|
||||
"jobs": [0]
|
||||
}
|
||||
```
|
||||
|
||||
`jobs[i]` 与 `profiles[i]` 一一对应。**同一个 job 只能有一个 profile** ——
|
||||
两个 profile 指向同一个 `.aic` 且内容不同会直接报错
|
||||
(`two profiles target ... with different artifacts`),因为一个人物职业只有
|
||||
一条 AIC 路径。
|
||||
|
||||
命令:
|
||||
|
||||
```bat
|
||||
companionpvf.exe --profile-overlay ^
|
||||
D:\90server\Server\runtime\data\dnf\Script.pvf ^
|
||||
D:\90server\Server\runtime\companion-overlay\Script.companion.pvf ^
|
||||
D:\90server\Server\runtime\companion-overlay\profiles.json
|
||||
```
|
||||
|
||||
输出 JSON 里带 `Manifest`,包含每个 artifact 的 `apc_code`/`aic_path`/
|
||||
`aic_text`/`sha256`/`strategy_status`/`missing_capabilities`。
|
||||
|
||||
## 三、职业 → APC / AIC 映射
|
||||
|
||||
来自 `internal/modules/dnf/companion/template.go` `aiTemplatePairs`:
|
||||
|
||||
| job | 职业 | APC | AIC 路径 |
|
||||
|---|---|---|---|
|
||||
| 0 | swordman 剑士 | **900** | `aicharacter/_bizarre/swordman/mirror_swordman/mirror_swordman.aic` |
|
||||
| 1 | fighter 格斗 | 5405 | `aicharacter/fighter/araf_fighter/araf_fighter_doll.aic` |
|
||||
| 2 | gunner 枪手 | 10903 | `aicharacter/_bizarre/gunner/mirror_gunner/mirror_gunner.aic` |
|
||||
| 3 | mage 法师 | 15902 | `aicharacter/_bizarre/magician/mirror_magician/mirror_magician.aic` |
|
||||
|
||||
本部署的支援兵「测试3」是 **job 0**,所以覆盖的是 APC **900** 的
|
||||
`mirror_swordman.aic`。
|
||||
|
||||
## 四、校验(独立于工具自身的回读)
|
||||
|
||||
工具内部已经回读校验过;需要再次人工确认时,用 `platformpvf` 打开两个归档:
|
||||
|
||||
- 文件数必须相同(只替换已有文件,不增删路径)
|
||||
- 目标 `.aic` 的字节必须不同
|
||||
- 目标 `.aic` 必须含 `CompanionSlot00`…`CompanionSlot11`、
|
||||
`CompanionDirectPool00`…、`CompanionBasicAttack`
|
||||
- 五个标量标签必须等于 `aic_text` 里的值:
|
||||
`[think term]` `[destination change term]` `[keep distance with target]`
|
||||
`[warlike]` `[vision]`
|
||||
|
||||
## 五、已知缺口(工具自己会报)
|
||||
|
||||
最近一次生成(job 0 / aggressive)结果:
|
||||
|
||||
```
|
||||
strategy_status = compiled_with_gaps
|
||||
missing_capabilities = [stuck_detour, target_sticky]
|
||||
```
|
||||
|
||||
含义:卡住绕行与目标粘性这两步**没有可用的原生能力回执**,不会被编译进
|
||||
AIC。其余(思考间隔、目的地切换、保持距离、好战度、视野)都已写入。
|
||||
|
||||
## 六、启用(**未自动执行,需人工确认**)
|
||||
|
||||
覆盖要生效,客户端必须加载这份 PVF —— DNF 只读 `Script.pvf` 这个固定文件名,
|
||||
所以是**替换**而不是追加:
|
||||
|
||||
1. 备份客户端 `D:\90server\Client\Script.pvf`(110,794,637 B)
|
||||
2. 用 `Script.companion.pvf` 覆盖 `D:\90server\Client\Script.pvf`
|
||||
3. 服务端 `runtime\data\dnf\Script.pvf` 也要一致
|
||||
(`Server\START.bat` / `Client\开始游戏.bat` 会在启动时按哈希把客户端 PVF
|
||||
同步到服务端,所以第 2 步做完通常会自动处理)
|
||||
|
||||
**回滚**:把备份的 `Script.pvf` 拷回去即可。
|
||||
|
||||
### 风险(务必知悉)
|
||||
|
||||
- 覆盖改的是 `aicharacter/_bizarre/swordman/mirror_swordman/mirror_swordman.aic`。
|
||||
`_bizarre` 目录下的 AIC 属于"镜像/特殊"类资源,**如果还有别的地方复用这条
|
||||
AIC**(例如某些副本的镜像敌人),它们的行为会一起改变。
|
||||
- PVF 是 105 MB 级文件,替换前确认磁盘余量。
|
||||
- 覆盖只影响客户端表现;服务端 `0x7F41` 投影里的档位参数仍然照常下发。
|
||||
|
||||
## 七、全职业覆盖(推荐做法)
|
||||
|
||||
`profiles.json` 的 `jobs` 可以一次给多个职业,工具会逐条编译并回读校验。
|
||||
当前部署已覆盖 **job 0–14 共 15 个职业**(`jobs: [0..14]`,全部 aggressive):
|
||||
|
||||
| job | 职业 | APC | AIC |
|
||||
|---|---|---|---|
|
||||
| 0 | swordman | 900 | `_bizarre/swordman/mirror_swordman/mirror_swordman.aic` |
|
||||
| 1 | fighter | 5405 | `fighter/arad_fighter/arad_fighter_doll.aic` |
|
||||
| 2 | gunner | 10903 | `_bizarre/gunner/mirror_gunner/mirror_gunner.aic` |
|
||||
| 3 | mage | 15902 | `_bizarre/magician/mirror_magician/mirror_magician.aic` |
|
||||
| 4 | priest | 20902 | `_bizarre/priest/mirror_priest/mirror_priest.aic` |
|
||||
| 5 | atgunner | 25900 | `_bizarre/atgunner/mirror_atgunner/mirror_atgunner.aic` |
|
||||
| 6 | thief | 30901 | `_bizarre/Thief/mirror_Thief/mirror_Thief.aic` |
|
||||
| 7 | atfighter | 35903 | `_bizarre/atfighter/mirror_atfighter/mirror_atfighter.aic` |
|
||||
| 8 | atmage | 40800 | `autocharacter/atmage/auto1/auto_atmage1.aic` |
|
||||
| 9 | demonic_swordman | 902 | `_bizarre/swordman/despair_darkknight/despair_darkknight.aic` |
|
||||
| 10 | creator | 15903 | `_bizarre/magician/despair_creator/despair_creator.aic` |
|
||||
| 11 | atswordman | 56901 | `_bizarre/atswordman/despair_atswordman/despair_atswordman.aic` |
|
||||
| 12 | knight | 60901 | `_bizarre/knight/despair_knight/despair_knight.aic` |
|
||||
| 13 | demonic_lancer | 65001 | `demoniclancer/mirror_demonicLancer/mirror_demonicLancer.aic` |
|
||||
| 14 | atpriest | 70003 | `atpriest/despair_atpriest/despair_atpriest.aic` |
|
||||
| **15** | **gunblader** | 75001 | **`gunblader/mirror_gunblader/mirror_gunblader.aic` —— 无法覆盖** |
|
||||
|
||||
16 条 AIC 路径互不相同,不会触发"两个 profile 指向同一 AIC"的冲突。
|
||||
|
||||
### job 15(gunblader)为什么覆盖不了
|
||||
|
||||
`companionAIRefs` 要求 `[ai pattern]` 段里同时存在 `action` / `destination` /
|
||||
`move` 三个 `.ai` 程序引用。实际内容对比:
|
||||
|
||||
```
|
||||
job 14 atpriest : `Event.ai` `DestinationSelect.ai` `Action.ai` `MoveMethod.ai`
|
||||
job 15 gunblader: `Action.h`
|
||||
```
|
||||
|
||||
gunblader 用的是**另一代 AIC 格式**:只引用一个 `.h` 头文件,没有独立的
|
||||
action/destination/move 程序槽,因此 AI 程序无处可写。这是格式代差,不是
|
||||
补丁器 bug —— 强行写入会破坏该 AIC。它的支援兵仍然能召唤并自带原版 AI,
|
||||
只是拿不到本工具的 12 格快捷栏/普攻程序。
|
||||
|
||||
## 八、两个必须知道的坑
|
||||
|
||||
### 1. `behavior: "assist"` 会让支援兵**站着不打**
|
||||
|
||||
```go
|
||||
func aicWarlike(profile AIProfile) int {
|
||||
if profile.Behavior == "aggressive" { return 80 }
|
||||
return 0 // assist 及其它一律 0
|
||||
}
|
||||
```
|
||||
|
||||
`[warlike] = 0` 就是不主动攻击。而且 assist 想要的能力(`leader_buff`、
|
||||
`leader_follow`)当前都缺回执,实测等于"不动"。**想让支援兵主动作战必须用
|
||||
`aggressive`。**
|
||||
|
||||
### 2. GM 克隆出来的角色,快照可能无效
|
||||
|
||||
GM 工具的"克隆角色"生成的记录不完全满足投影约束,preflight 会报:
|
||||
|
||||
```
|
||||
reason = snapshot_invalid
|
||||
message = 所选角色的装备/技能快照不完整或不符合投影约束
|
||||
ai_template = @{key=; apc_code=0; aic_path=; ...} ← 全空
|
||||
snapshot_at = 0001-01-01T00:00:00Z
|
||||
```
|
||||
|
||||
这类角色**不能**当支援兵。换一个正常创建的角色即可(`preflight` 会显示
|
||||
每个源的 `ready` / `reason`)。
|
||||
|
||||
|
||||
## 九、按键程序必须重写("支援兵不放技能"的根因)
|
||||
|
||||
`--profile-overlay` 原来**只替换 AIC + 4 个 `.ai` 程序**,**没有重写 key 文件本体**;
|
||||
legacy 路径(`main.go` 里 `keyFiles` / `directPoolFiles` 两个循环)一直在重写。
|
||||
这个差异导致支援兵能召唤、能跟随、却不放技能。
|
||||
|
||||
### 症状(客户端 `90CN_companion_p0.log`)
|
||||
|
||||
```
|
||||
runtime-key projection blocked reason=direct-bytecode-shape slot=0 ... size=128
|
||||
projection APC registered ... runtime_keys_applied=0
|
||||
```
|
||||
|
||||
### 原因
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 客户端默认绑定表要求 | `kRuntimeDirectKeyBytecodeSize = 0x180`(384 字节)|
|
||||
| 客户端 DB4 表要求 | `0x1BC`(444 字节)|
|
||||
| DB4 那批 25 字节旧 key 编译后 | **128 字节 (0x80)** |
|
||||
| 客户端占位技能码 | `kSkillCodeBias(0x100) + kPlaceholderSkillId(511)` = **0x2FF** |
|
||||
|
||||
AIC 的 `[key stream]` 引用的是 `magician/biske2` 与 `magician/flea_inflame_hands2`
|
||||
下那批 25 字节 key —— 它们是给 DB4 客户端做的。零售客户端把它编译成 128 字节,
|
||||
尺寸校验直接拒绝,于是 `runtime_keys_applied=0`,12 格快捷栏没有绑定,AIC 无键可按。
|
||||
|
||||
### 修复
|
||||
|
||||
在 `BuildProfileOverlay` 重打包前,补上与 legacy 完全一致的两个循环:
|
||||
|
||||
```go
|
||||
for index, path := range companionFrontKeyFiles {
|
||||
appendReplacement(RawReplacement{Path: path,
|
||||
Data: buildQuickSlotKey(archive, companionQuickSlotKeys[index])})
|
||||
}
|
||||
for _, path := range companionDirectKeyFiles {
|
||||
appendReplacement(RawReplacement{Path: path,
|
||||
Data: buildDirectPlaceholderKey(archive)})
|
||||
}
|
||||
```
|
||||
|
||||
`companionQuickSlotKeys = a s d f g h q w e r t y`(前段 12 格要按的键)。
|
||||
|
||||
生成结果:
|
||||
|
||||
| key | 原始 | 重写后 | 内容 |
|
||||
|---|---|---|---|
|
||||
| `biske2/key/*.key` | 25 B | 50 B | `[input] `target current direction` 100 `time` 50 [input] `a` 30 `time` 100` |
|
||||
| `flame_hands2/key/*.key` | 25 B | 80 B | `... [input] `skill:511` 100 `time` 200` |
|
||||
|
||||
### 验证成功的标志
|
||||
|
||||
```
|
||||
projection APC registered key=1 ... runtime_keys_applied=1
|
||||
projection APC registered key=3 ... runtime_keys_applied=1
|
||||
```
|
||||
|
||||
`runtime_keys_applied` 从 0 变 1,且日志里不再出现 `direct-bytecode-shape`。
|
||||
|
||||
### 覆盖范围
|
||||
|
||||
`已覆盖职业: 15 / 16` —— job 0–14 全部 25 组键流 + `CompanionSlot00`。
|
||||
**job 15(gunblader 枪剑士)无法覆盖**:它的 `[ai pattern]` 只有 `` `Action.h` ``
|
||||
单头文件,没有 action/destination/move 三个程序槽,`companionAIRefs` 直接报错,
|
||||
`companionpvf` 不会为它生成任何东西。它的支援兵仍可召唤,但拿不到支援兵 AI 与
|
||||
12 格快捷栏程序。
|
||||
|
||||
### key 文件是共享资产
|
||||
|
||||
重写的 24 个 key 文件是**全局共享**的,所以一次重写对所有已覆盖职业同时生效;
|
||||
代价是 `mirror_magician` 等原本用这批 key 的 APC 的按键行为也会跟着变(官方
|
||||
legacy 路径同样如此)。
|
||||
Reference in new issue
Block a user