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,208 @@
|
||||
package raid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// lukeCardDisplayTestSanctums is the shipped phase-one shape: three sanctums,
|
||||
// each with its own pattern index and seven card slots.
|
||||
func lukeCardDisplayTestSanctums() []LukeCardDisplaySanctum {
|
||||
return []LukeCardDisplaySanctum{
|
||||
{
|
||||
DungeonID: 5054,
|
||||
Pattern: 1,
|
||||
Cards: []uint32{59171, 59098, 59873, 59096, 56065, 59874, 56699},
|
||||
},
|
||||
{
|
||||
DungeonID: 5055,
|
||||
Pattern: 2,
|
||||
Cards: []uint32{59278, 56166, 56673, 59875, 56066, 56674, 56067},
|
||||
},
|
||||
{
|
||||
DungeonID: 5056,
|
||||
Pattern: 0,
|
||||
Cards: []uint32{56068, 56063, 56700, 57156, 59872, 59099, 59095},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildLukeCardDisplayBodyMatchesTheReverseEngineeredWireBytes is the
|
||||
// byte-exact pin for opcode 1181. Every expected byte below is written out by
|
||||
// hand from the spec's field table (tools\_rev_卢克塔罗牌_1181规格_20260929.md
|
||||
// §一) rather than re-encoded by the builder:
|
||||
//
|
||||
// u8 cardSetIndex = 3
|
||||
// u8 N = 3
|
||||
// per record: u16 A (little endian), u8 B, u8 K, then K x (u32 P little
|
||||
// endian, u8 Q = 0)
|
||||
// u8 openWindowFlag = 1
|
||||
//
|
||||
// 5054 = 0x13BE, 5055 = 0x13BF, 5056 = 0x13C0; the card ids are the 22
|
||||
// `[CARD]` monster ids, 59171 = 0x0000E723 and so on.
|
||||
func TestBuildLukeCardDisplayBodyMatchesTheReverseEngineeredWireBytes(t *testing.T) {
|
||||
body, err := BuildLukeCardDisplayBody(LukeCardDisplay{
|
||||
CardSetIndex: 3,
|
||||
CardSetCount: 5,
|
||||
Sanctums: lukeCardDisplayTestSanctums(),
|
||||
OpenWindow: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildLukeCardDisplayBody error = %v", err)
|
||||
}
|
||||
const wantHex = "" +
|
||||
// cardSetIndex, N
|
||||
"0303" +
|
||||
// 5054, pattern 1, K=7, then the seven cards each followed by Q=0
|
||||
"be13" + "01" + "07" +
|
||||
"23e70000" + "00" +
|
||||
"dae60000" + "00" +
|
||||
"e1e90000" + "00" +
|
||||
"d8e60000" + "00" +
|
||||
"01db0000" + "00" +
|
||||
"e2e90000" + "00" +
|
||||
"7bdd0000" + "00" +
|
||||
// 5055, pattern 2, K=7
|
||||
"bf13" + "02" + "07" +
|
||||
"8ee70000" + "00" +
|
||||
"66db0000" + "00" +
|
||||
"61dd0000" + "00" +
|
||||
"e3e90000" + "00" +
|
||||
"02db0000" + "00" +
|
||||
"62dd0000" + "00" +
|
||||
"03db0000" + "00" +
|
||||
// 5056, pattern 0, K=7
|
||||
"c013" + "00" + "07" +
|
||||
"04db0000" + "00" +
|
||||
"ffda0000" + "00" +
|
||||
"7cdd0000" + "00" +
|
||||
"44df0000" + "00" +
|
||||
"e0e90000" + "00" +
|
||||
"dbe60000" + "00" +
|
||||
"d7e60000" + "00" +
|
||||
// openWindowFlag
|
||||
"01"
|
||||
want, err := hex.DecodeString(wantHex)
|
||||
if err != nil {
|
||||
t.Fatalf("decode want hex: %v", err)
|
||||
}
|
||||
if len(body) != len(want) {
|
||||
t.Fatalf("body_len=%d want=%d", len(body), len(want))
|
||||
}
|
||||
if got := hex.EncodeToString(body); got != wantHex {
|
||||
t.Fatalf("body=%s\nwant=%s", got, wantHex)
|
||||
}
|
||||
if size := LukeCardDisplayBodySize(lukeCardDisplayTestSanctums()); size != len(want) {
|
||||
t.Fatalf("LukeCardDisplayBodySize=%d want=%d", size, len(want))
|
||||
}
|
||||
|
||||
// Re-read the body field by field, in the client's own order, so a change in
|
||||
// any field width fails here with the offset rather than only as a hex diff.
|
||||
if body[0] != 3 {
|
||||
t.Fatalf("cardSetIndex=%d want 3", body[0])
|
||||
}
|
||||
if body[1] != 3 {
|
||||
t.Fatalf("N=%d want 3", body[1])
|
||||
}
|
||||
offset := 2
|
||||
for index, want := range lukeCardDisplayTestSanctums() {
|
||||
if got := binary.LittleEndian.Uint16(body[offset : offset+2]); uint32(got) != want.DungeonID {
|
||||
t.Fatalf("record %d A@%d=%d want %d", index, offset, got, want.DungeonID)
|
||||
}
|
||||
offset += 2
|
||||
if int(body[offset]) != want.Pattern {
|
||||
t.Fatalf("record %d B@%d=%d want %d", index, offset, body[offset], want.Pattern)
|
||||
}
|
||||
offset++
|
||||
if int(body[offset]) != 7 {
|
||||
t.Fatalf("record %d K@%d=%d want 7", index, offset, body[offset])
|
||||
}
|
||||
offset++
|
||||
for slot, cardID := range want.Cards {
|
||||
if got := binary.LittleEndian.Uint32(body[offset : offset+4]); got != cardID {
|
||||
t.Fatalf("record %d slot %d P@%d=%d want %d", index, slot, offset, got, cardID)
|
||||
}
|
||||
offset += 4
|
||||
if body[offset] != 0 {
|
||||
t.Fatalf("record %d slot %d Q@%d=%d want 0", index, slot, offset, body[offset])
|
||||
}
|
||||
offset++
|
||||
}
|
||||
}
|
||||
if offset != len(body)-1 {
|
||||
t.Fatalf("openWindowFlag offset=%d want=%d", offset, len(body)-1)
|
||||
}
|
||||
if body[len(body)-1] != 1 {
|
||||
t.Fatalf("openWindowFlag=%d want 1", body[len(body)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildLukeCardDisplayBodyKeepsOpenWindowOffWhenNotAsked pins the trailing
|
||||
// byte's other value: only the waiting-area push sets it, later re-reads do not.
|
||||
func TestBuildLukeCardDisplayBodyKeepsOpenWindowOffWhenNotAsked(t *testing.T) {
|
||||
body, err := BuildLukeCardDisplayBody(LukeCardDisplay{
|
||||
CardSetIndex: 0,
|
||||
CardSetCount: 5,
|
||||
Sanctums: []LukeCardDisplaySanctum{{DungeonID: 5054, Pattern: 0, Cards: []uint32{59171}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildLukeCardDisplayBody error = %v", err)
|
||||
}
|
||||
want := []byte{0x00, 0x01, 0xbe, 0x13, 0x00, 0x01, 0x23, 0xe7, 0x00, 0x00, 0x00, 0x00}
|
||||
if got := hex.EncodeToString(body); got != hex.EncodeToString(want) {
|
||||
t.Fatalf("body=%s want=%s", got, hex.EncodeToString(want))
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildLukeCardDisplayBodyRefusesBodiesTheClientWouldDrop covers the
|
||||
// out-of-range card set index (the handler throws the WHOLE packet away) and the
|
||||
// shapes that would make the client read past the end of a record.
|
||||
func TestBuildLukeCardDisplayBodyRefusesBodiesTheClientWouldDrop(t *testing.T) {
|
||||
base := func() LukeCardDisplay {
|
||||
return LukeCardDisplay{
|
||||
CardSetIndex: 4,
|
||||
CardSetCount: 5,
|
||||
Sanctums: lukeCardDisplayTestSanctums(),
|
||||
OpenWindow: true,
|
||||
}
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
mutate func(*LukeCardDisplay)
|
||||
}{
|
||||
{"card set index at the bound", func(d *LukeCardDisplay) { d.CardSetIndex = 5 }},
|
||||
{"unknown card set count", func(d *LukeCardDisplay) { d.CardSetCount = 0 }},
|
||||
{"no records", func(d *LukeCardDisplay) { d.Sanctums = nil }},
|
||||
{"zero dungeon id", func(d *LukeCardDisplay) { d.Sanctums[0].DungeonID = 0 }},
|
||||
{"negative pattern", func(d *LukeCardDisplay) { d.Sanctums[0].Pattern = -1 }},
|
||||
{"pattern above the u8", func(d *LukeCardDisplay) { d.Sanctums[0].Pattern = 256 }},
|
||||
{"no slots", func(d *LukeCardDisplay) { d.Sanctums[0].Cards = nil }},
|
||||
{"zero card id", func(d *LukeCardDisplay) { d.Sanctums[0].Cards[3] = 0 }},
|
||||
{"too many records", func(d *LukeCardDisplay) {
|
||||
records := make([]LukeCardDisplaySanctum, 256)
|
||||
for index := range records {
|
||||
records[index] = LukeCardDisplaySanctum{DungeonID: 5054, Cards: []uint32{59171}}
|
||||
}
|
||||
d.Sanctums = records
|
||||
}},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
display := base()
|
||||
testCase.mutate(&display)
|
||||
if _, err := BuildLukeCardDisplayBody(display); !errors.Is(err, ErrLukeCardDisplayShapeInvalid) {
|
||||
t.Fatalf("%s: err=%v want %v", testCase.name, err, ErrLukeCardDisplayShapeInvalid)
|
||||
}
|
||||
}
|
||||
if _, err := BuildLukeCardDisplayBody(base()); err != nil {
|
||||
t.Fatalf("the unmutated shape must still encode: %v", err)
|
||||
}
|
||||
if LukeCardDisplayMsgID != 1181 {
|
||||
t.Fatalf("LukeCardDisplayMsgID=%d want 1181", LukeCardDisplayMsgID)
|
||||
}
|
||||
if strings.TrimSpace(LukeCardSetPVFPath) != "etc/raid/luke/LukeCardSet.etc" {
|
||||
t.Fatalf("LukeCardSetPVFPath=%q", LukeCardSetPVFPath)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user