Files
90CNServer/server/internal/services/dnfbridge/stat_change_potion_projection_test.go
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

109 lines
5.0 KiB
Go
Raw Permalink 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.
package dnfbridge
import (
"bytes"
"testing"
)
// 城镇增益药剂(史诗Buff药剂一族)的 buff 注册包 —— 图标就是靠它出现的。
//
// 协议号在**本客户端**里定标(2026-10-03 第四轮,服主提醒"A21 与我们客户端不同,
// 不能照抄"):客户端启动时把 294 个 ENUM_NOTIPACKET_* 名字按枚举顺序注册进
// 0x52bc170 + 4*i(tools/_gb_noti_table_re.py),id = base + slot,而 base = 485 —— 用
// 本仓库现场验证过的号反查得到:
//
// slot 98..102 = RAID_SET_SYMBOL..RAID_STATE ↔ raid/body.go 0x247..0x24b (583..587)
// slot 95 = SEND_DECK_INFO ↔ guardian shield deck 通知 580
//
// ⇒ ADD_BUFF = 487、DEL_BUFF = 488、BUFF_DUNGEON = 489(旧代码用的 488/489/490 是
// A21 的 486 基址,在本客户端分别落在 DEL_BUFF / BUFF_DUNGEON / 疲劳值加速状态上,
// 等于 ADD_BUFF 从未发出去过)。
//
// 现场 2026-10-03:只发 class0/op174(效果道具倒计时列表)时,客户端只弹
// "[史诗Buff药剂]的效果时间还剩10分钟",buff 栏仍为空 —— 两套东西,必须都发。
func TestCharacterAddBuffBodyMatchesTheReferenceLayout(t *testing.T) {
add := buildCurrentCharacterAddBuffBody(1136)
want := []byte{1, 0x70, 0x04, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
if !bytes.Equal(add, want) {
t.Fatalf("ADD_BUFF body=%x want=%x", add, want)
}
if len(add) != 17 {
t.Fatalf("ADD_BUFF body_len=%d want 17 (u8 count + i32 buffId + 3*i32)", len(add))
}
remove := buildCurrentCharacterRemoveBuffBody([]int32{1240, 0, 1239})
wantRemove := []byte{2, 0xD8, 0x04, 0x00, 0x00, 0xD7, 0x04, 0x00, 0x00}
if !bytes.Equal(remove, wantRemove) {
t.Fatalf("REMOVE_BUFF body=%x want=%x(0 与非正 id 必须被跳过,count 只数有效行)", remove, wantRemove)
}
if body := buildCurrentCharacterRemoveBuffBody(nil); body != nil {
t.Fatalf("empty remove body=%x want nil", body)
}
}
// 大瓶/小瓶/赛丽亚三档的 buff id(PVF 表查出来的真实值)也必须编码正确。
func TestCharacterAddBuffBodyCoversEveryFamilyBuffID(t *testing.T) {
for _, tc := range []struct {
buffID int32
want []byte
}{
{buffID: 1136, want: []byte{0x70, 0x04, 0x00, 0x00}},
{buffID: 1239, want: []byte{0xD7, 0x04, 0x00, 0x00}},
{buffID: 1240, want: []byte{0xD8, 0x04, 0x00, 0x00}},
{buffID: 1090, want: []byte{0x42, 0x04, 0x00, 0x00}},
{buffID: 1092, want: []byte{0x44, 0x04, 0x00, 0x00}},
} {
body := buildCurrentCharacterAddBuffBody(tc.buffID)
if body[0] != 1 || !bytes.Equal(body[1:5], tc.want) {
t.Fatalf("buff %d body=%x want buff id bytes %x", tc.buffID, body, tc.want)
}
for index := 5; index < len(body); index++ {
if body[index] != 0 {
t.Fatalf("buff %d body=%x: 三个状态字段必须为 0", tc.buffID, body)
}
}
}
}
// CHARACTER_BUFF_DUNGEON(class0/op489)是"激活"那一发:u16 count + u32 buffId 列表。
// 参考实现注释:ADD_BUFF 只创建/更新条目,BUFF_DUNGEON 才 toggles existing entries active,
// 且列表必须是完整激活集合(客户端先清空本地标志再按列表置位)。
func TestCharacterBuffDungeonBodyLayout(t *testing.T) {
active := buildCurrentCharacterBuffDungeonBody([]int32{1136})
want := []byte{1, 0x00, 0x70, 0x04, 0x00, 0x00}
if !bytes.Equal(active, want) {
t.Fatalf("activate body=%x want=%x", active, want)
}
empty := buildCurrentCharacterBuffDungeonBody(nil)
if !bytes.Equal(empty, []byte{0, 0}) {
t.Fatalf("empty activate body=%x want 0000(清空激活标志)", empty)
}
multi := buildCurrentCharacterBuffDungeonBody([]int32{1090, 0, 1092})
if !bytes.Equal(multi, []byte{2, 0x00, 0x42, 0x04, 0x00, 0x00, 0x44, 0x04, 0x00, 0x00}) {
t.Fatalf("multi activate body=%x(非正 id 必须跳过,count 只数有效行)", multi)
}
}
// TestCharacterBuffNotificationMsgIDsArePinnedOnThisClient 用**字面量**钉住三个通知号。
//
// 这里刻意不写成 `if currentCharacterAddBuffMsgID != someConst`:常量对常量是恒真的
// 空断言。本客户端(90CN,DNF.exe)的 NOTIPACKET 基址是 485,不是参考服务端 A21 的
// 486 —— 照抄 A21 会把 ADD_BUFF 发成 DEL_BUFF(数值 488),图标永远不会出现。
//
// 定标证据(全部是本仓库现场验证过的号,见 stat_change_potion_projection.go 顶部注释):
//
// slot 98 RAID_SET_SYMBOL ↔ 583 slot 102 RAID_STATE ↔ 587
// slot 95 SEND_DECK_INFO ↔ 580(守护者盾牌牌组通知)
// ⇒ base = 485 ⇒ slot 2 CHARACTER_ADD_BUFF = 487
func TestCharacterBuffNotificationMsgIDsArePinnedOnThisClient(t *testing.T) {
if currentCharacterAddBuffMsgID != 487 {
t.Fatalf("CHARACTER_ADD_BUFF msg_id=%d want 487", currentCharacterAddBuffMsgID)
}
if currentCharacterRemoveBuffMsgID != 488 {
t.Fatalf("CHARACTER_DEL_BUFF msg_id=%d want 488", currentCharacterRemoveBuffMsgID)
}
if currentCharacterBuffDungeonMsgID != 489 {
t.Fatalf("CHARACTER_BUFF_DUNGEON msg_id=%d want 489", currentCharacterBuffDungeonMsgID)
}
}