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,144 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/raid"
|
||||
)
|
||||
|
||||
// op1460's per-row handler (0x009AF190) rejects any key outside 81..125
|
||||
// (`lea ecx,[eax-0x51]` / `cmp ecx,0x2c` / `ja default`), and those ids are the
|
||||
// Fiend War state symbols from `etc/raid/fiendwar/fiendwar.etc.symbol`:
|
||||
// 81 radar / 82 battery / 83..88 outposts and 兵站本营 / 92..96 boss state,
|
||||
// mechanism digits and boss HP / 110,111 the BOSS display HP.
|
||||
//
|
||||
// Before this the board only routed `key >= 121` to 1460, so 81..111 travelled
|
||||
// on op583 and the client read none of them: the radar/battery effects, the boss
|
||||
// state and the boss HP bar were all blank.
|
||||
func TestFiendWarBoardRoutesTheStateSymbolsOnOp1460(t *testing.T) {
|
||||
now := time.Unix(1700000000, 0)
|
||||
const (
|
||||
areaStateKey = 79 // last [n AREA STATE] symbol -- stays on op583
|
||||
radarStateKey = 81 // 雷达
|
||||
batteryKey = 82 // 炮塔
|
||||
commandCenter = 88 // 南部兵站本营
|
||||
patternState = 94 // BOSS 机制位
|
||||
bossViewHPKey = 110 // TOP BOSS VIEW HP
|
||||
movieCenterKey = 125 // MOVIE COMMAND CENTER ON
|
||||
outOfRangeKey = 126 // outside the client's window, for either opcode
|
||||
)
|
||||
b := &raid.FiendWarBattle{
|
||||
Config: &raid.FiendWarBattleConfig{WaitingArea: 2, Areas: map[int]raid.FiendWarArea{}},
|
||||
Areas: map[int]raid.FiendWarAreaState{},
|
||||
GroupAreas: map[byte]int{1: 14},
|
||||
MemberAreas: map[uint16]int{1: 14},
|
||||
RecoveryUntil: map[uint16]time.Time{},
|
||||
Symbols: map[uint32]int{
|
||||
areaStateKey: 1,
|
||||
radarStateKey: 1,
|
||||
batteryKey: 1,
|
||||
commandCenter: 1,
|
||||
patternState: 1111,
|
||||
bossViewHPKey: 100,
|
||||
outOfRangeKey: 7,
|
||||
},
|
||||
}
|
||||
state := runtimeRaidState{
|
||||
RaidKey: 1,
|
||||
FrozenMembers: []runtimeRaidMemberState{{CharID: 1, GroupIndex: 1}},
|
||||
Lifecycle: runtimeRaidLifecycleState{FiendWar: b},
|
||||
}
|
||||
|
||||
nativeRows := func(packets []runtimeRaidWireNotification) map[uint32]uint32 {
|
||||
for _, packet := range packets {
|
||||
if packet.msgID != 1460 {
|
||||
continue
|
||||
}
|
||||
rows := make(map[uint32]uint32)
|
||||
for offset := 1; offset+8 <= len(packet.body); offset += 8 {
|
||||
rows[binary.LittleEndian.Uint32(packet.body[offset:])] = binary.LittleEndian.Uint32(packet.body[offset+4:])
|
||||
}
|
||||
return rows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
symbolRows := func(packets []runtimeRaidWireNotification) map[uint32]uint32 {
|
||||
for _, packet := range packets {
|
||||
if packet.msgID != raid.RaidSymbolMsgID {
|
||||
continue
|
||||
}
|
||||
rows := make(map[uint32]uint32)
|
||||
for offset := 1; offset+8 <= len(packet.body); offset += 8 {
|
||||
rows[binary.LittleEndian.Uint32(packet.body[offset:])] = binary.LittleEndian.Uint32(packet.body[offset+4:])
|
||||
}
|
||||
return rows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Frame 1: the whole board is new, so the state symbols must be present.
|
||||
first, err := runtimeFiendWarBoardPackets(state, runtimeRaidState{}, 0, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
native := nativeRows(first)
|
||||
if native == nil {
|
||||
t.Fatal("no op1460 packet was built")
|
||||
}
|
||||
for key, want := range map[uint32]uint32{
|
||||
radarStateKey: 1,
|
||||
batteryKey: 1,
|
||||
commandCenter: 1,
|
||||
patternState: 1111,
|
||||
bossViewHPKey: 100,
|
||||
} {
|
||||
got, ok := native[key]
|
||||
if !ok {
|
||||
t.Errorf("symbol %d was not routed to op1460 (the client drops it on op583)", key)
|
||||
continue
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("symbol %d on op1460 = %d, want %d", key, got, want)
|
||||
}
|
||||
}
|
||||
for _, key := range []uint32{areaStateKey, outOfRangeKey} {
|
||||
if _, ok := native[key]; ok {
|
||||
t.Errorf("symbol %d must stay off op1460 (outside the client's 81..125 window)", key)
|
||||
}
|
||||
}
|
||||
// The legacy op583 route is untouched, and this change is deliberately
|
||||
// ADDITIVE: 81..120 ride both opcodes for now, so nothing that used to work
|
||||
// can regress while the new route is being proven on the live client.
|
||||
// Trimming op583 to `key < 81` is a later, separate experiment.
|
||||
symbols := symbolRows(first)
|
||||
if symbols == nil {
|
||||
t.Fatal("no op583 symbol table was built")
|
||||
}
|
||||
if _, ok := symbols[areaStateKey]; !ok {
|
||||
t.Errorf("symbol %d must still be carried by op583", areaStateKey)
|
||||
}
|
||||
if _, ok := symbols[radarStateKey]; !ok {
|
||||
t.Errorf("symbol %d stays on op583 while the op1460 route is unproven", radarStateKey)
|
||||
}
|
||||
// 121..125 were never on op583 and must not become op583 traffic.
|
||||
if _, ok := symbols[movieCenterKey]; ok {
|
||||
t.Errorf("symbol %d is op1460-only traffic and must not ride op583", movieCenterKey)
|
||||
}
|
||||
|
||||
// Frame 2: the 121..125 one-shots keep their "first frame, non-zero only"
|
||||
// rule, so they appear when they change and not before.
|
||||
previous := cloneRuntimeRaidState(state)
|
||||
b.Symbols[movieCenterKey] = 1
|
||||
second, err := runtimeFiendWarBoardPackets(state, previous, 0, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := nativeRows(second)[movieCenterKey]; got != 1 {
|
||||
t.Errorf("symbol %d after its change = %d, want 1 routed to op1460", movieCenterKey, got)
|
||||
}
|
||||
if _, ok := nativeRows(first)[movieCenterKey]; ok {
|
||||
t.Errorf("symbol %d must not be replayed on the first frame while zero", movieCenterKey)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user