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,111 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeCurrentDungeonCardRequestsUseExactCurrentShapes(t *testing.T) {
|
||||
if err := validateCurrentDungeonBodylessRequest(nil); err != nil {
|
||||
t.Fatalf("bodyless request: %v", err)
|
||||
}
|
||||
if err := validateCurrentDungeonBodylessRequest([]byte{0}); !errors.Is(err, errCurrentDungeonCardPacketShape) {
|
||||
t.Fatalf("non-bodyless error = %v", err)
|
||||
}
|
||||
op71, err := decodeCurrentDungeonOp71Request([]byte{2, 3})
|
||||
if err != nil || op71.ValueA != 2 || op71.ValueB != 3 || op71.ValueCount != 2 {
|
||||
t.Fatalf("op71 = %+v err=%v", op71, err)
|
||||
}
|
||||
op71, err = decodeCurrentDungeonOp71Request([]byte{7})
|
||||
if err != nil || op71.ValueA != 7 || op71.ValueB != 0 || op71.ValueCount != 1 {
|
||||
t.Fatalf("one-byte op71 = %+v err=%v", op71, err)
|
||||
}
|
||||
// The legacy game transport pads the op71 body (live bodies run 8 bytes);
|
||||
// the decoder must read the proved one/two-byte prefix and ignore the rest.
|
||||
op71, err = decodeCurrentDungeonOp71Request([]byte{2, 3, 0, 0, 0, 0, 0, 0})
|
||||
if err != nil || op71.ValueA != 2 || op71.ValueB != 3 || op71.ValueCount != 2 {
|
||||
t.Fatalf("padded op71 = %+v err=%v", op71, err)
|
||||
}
|
||||
for _, invalid := range [][]byte{nil, {}} {
|
||||
if _, err := decodeCurrentDungeonOp71Request(invalid); !errors.Is(err, errCurrentDungeonCardPacketShape) {
|
||||
t.Fatalf("invalid op71 body=%x error=%v", invalid, err)
|
||||
}
|
||||
}
|
||||
op72, err := decodeCurrentDungeonOp72Request([]byte{1, 2})
|
||||
if err != nil || op72.ValueA != 1 || op72.ValueB != 2 {
|
||||
t.Fatalf("op72 = %+v err=%v", op72, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonOp69SuccessIsOnlyCommonSuccess(t *testing.T) {
|
||||
if body := buildCurrentDungeonOp69SuccessBody(); len(body) != 1 || body[0] != 1 {
|
||||
t.Fatalf("op69 success = %x", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonOp70SuccessMatchesBothConditionalReaderShapes(t *testing.T) {
|
||||
if body := buildCurrentDungeonOp70CommonOnlySuccessBody(); len(body) != 1 || body[0] != 1 {
|
||||
t.Fatalf("op70 common-only success = %x", body)
|
||||
}
|
||||
values := [dungeonCardWireSlotCount]uint16{0x0102, 0x0304, 0x0506, 0x0708, 0x090A, 0x0B0C, 0x0D0E, 0x0F10}
|
||||
body := buildCurrentDungeonOp70EightValueSuccessBody(values)
|
||||
if len(body) != 17 || body[0] != 1 {
|
||||
t.Fatalf("op70 eight-value len/success = %d/%d", len(body), body[0])
|
||||
}
|
||||
for index, want := range values {
|
||||
if got := binary.LittleEndian.Uint16(body[1+index*2 : 1+(index+1)*2]); got != want {
|
||||
t.Fatalf("op70 value[%d]=%04x want=%04x", index, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonOp71SuccessMatchesEightSlotReader(t *testing.T) {
|
||||
var slots [dungeonCardWireSlotCount]currentDungeonOp71Slot
|
||||
slots[0] = currentDungeonOp71Slot{
|
||||
StateA: 0x10,
|
||||
StateB: 0x11,
|
||||
Rewards: []currentDungeonOp71RewardTuple{
|
||||
{ValueA: 0x11223344, ValueB: 0x55667788},
|
||||
},
|
||||
TerminalFlag: 0x12,
|
||||
}
|
||||
for index := 1; index < len(slots); index++ {
|
||||
slots[index].StateA = byte(index)
|
||||
slots[index].StateB = byte(index + 0x20)
|
||||
slots[index].TerminalFlag = byte(index + 0x40)
|
||||
}
|
||||
body, err := buildCurrentDungeonOp71SuccessBody(slots)
|
||||
if err != nil {
|
||||
t.Fatalf("build op71: %v", err)
|
||||
}
|
||||
if len(body) != 41 || body[0] != 1 {
|
||||
t.Fatalf("op71 len/success = %d/%d", len(body), body[0])
|
||||
}
|
||||
if body[1] != 0x10 || body[2] != 0x11 || body[3] != 1 || binary.LittleEndian.Uint32(body[4:8]) != 0x11223344 || binary.LittleEndian.Uint32(body[8:12]) != 0x55667788 || body[12] != 0x12 {
|
||||
t.Fatalf("op71 first slot = %x", body[1:13])
|
||||
}
|
||||
if body[13] != 1 || body[14] != 0x21 || body[15] != 0 || body[16] != 0x41 {
|
||||
t.Fatalf("op71 second slot = %x", body[13:17])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonOp71MinimumBodyIs33Bytes(t *testing.T) {
|
||||
var slots [dungeonCardWireSlotCount]currentDungeonOp71Slot
|
||||
body, err := buildCurrentDungeonOp71SuccessBody(slots)
|
||||
if err != nil {
|
||||
t.Fatalf("build empty op71: %v", err)
|
||||
}
|
||||
if len(body) != 33 {
|
||||
t.Fatalf("minimum op71 body len = %d, want 33", len(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonOp72BodiesMatchReader(t *testing.T) {
|
||||
if got := buildCurrentDungeonOp72SuccessBody(7, 9); len(got) != 3 || got[0] != 1 || got[1] != 7 || got[2] != 9 {
|
||||
t.Fatalf("op72 success = %x", got)
|
||||
}
|
||||
if got := buildCurrentDungeonOp72FailureBody(4); len(got) != 2 || got[0] != 0 || got[1] != 4 {
|
||||
t.Fatalf("op72 failure = %x", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user