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,135 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/worldmap"
|
||||
)
|
||||
|
||||
func TestDecodeCurrentDungeonAltarDeathSequencesStrictCurrentEXELayout(t *testing.T) {
|
||||
body := []byte{3, 1, 0, 0x34, 0x12, 0xff, 0xff}
|
||||
sequences, err := decodeCurrentDungeonAltarDeathSequences(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []uint16{1, 0x1234, 0xffff}
|
||||
if len(sequences) != len(want) {
|
||||
t.Fatalf("sequences=%v want=%v", sequences, want)
|
||||
}
|
||||
for index := range want {
|
||||
if sequences[index] != want[index] {
|
||||
t.Fatalf("sequences=%v want=%v", sequences, want)
|
||||
}
|
||||
}
|
||||
|
||||
malformed := [][]byte{
|
||||
nil,
|
||||
{0},
|
||||
{1, 1},
|
||||
{1, 0, 0},
|
||||
{2, 1, 0, 1, 0},
|
||||
append([]byte{101}, make([]byte, 202)...),
|
||||
}
|
||||
for _, candidate := range malformed {
|
||||
if _, err := decodeCurrentDungeonAltarDeathSequences(candidate); !errors.Is(err, errCurrentDungeonAltarRequestMalformed) {
|
||||
t.Fatalf("malformed body=%x error=%v", candidate, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonAltarStartMapCurrentEXEGolden(t *testing.T) {
|
||||
scene := worldmap.DungeonRoomScene{
|
||||
Coordinate: worldmap.RoomCoordinate{X: 2, Y: 3},
|
||||
Map: worldmap.ResolvedMap{Map: worldmap.Map{ID: 17030}},
|
||||
}
|
||||
first, err := buildCurrentDungeonAltarStartMapBody(scene, 0x12345678, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantFirst := []byte{2, 3, 0x78, 0x56, 0x34, 0x12, 0, 1, 0x86, 0x42, 0, 0, 0, 0, 0}
|
||||
if !bytes.Equal(first, wantFirst) {
|
||||
t.Fatalf("first op211=%x want=%x", first, wantFirst)
|
||||
}
|
||||
revisit, err := buildCurrentDungeonAltarStartMapBody(scene, 0x12345678, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantRevisit := []byte{2, 3, 0x78, 0x56, 0x34, 0x12, 0, 0}
|
||||
if !bytes.Equal(revisit, wantRevisit) {
|
||||
t.Fatalf("revisit op211=%x want=%x", revisit, wantRevisit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonAltarKnownClass0Bodies(t *testing.T) {
|
||||
crazy, err := buildCurrentDungeonAltarCrazyMonsterBody(currentDungeonAltarCrazyMonster{
|
||||
MonsterCode: 61010,
|
||||
Variant: 18,
|
||||
})
|
||||
if err != nil || !bytes.Equal(crazy, []byte{0x52, 0xee, 0, 0, 18}) {
|
||||
t.Fatalf("op213=%x err=%v", crazy, err)
|
||||
}
|
||||
interval := buildCurrentDungeonAltarRoundIntervalBody(4, 6000)
|
||||
if !bytes.Equal(interval, []byte{5, 0x70, 0x17, 0, 0}) {
|
||||
t.Fatalf("op215=%x", interval)
|
||||
}
|
||||
prompt := buildCurrentDungeonAltarDifficultyPromptBody(3, 10)
|
||||
if !bytes.Equal(prompt, []byte{0, 3, 10, 0}) {
|
||||
t.Fatalf("op297 prompt=%x", prompt)
|
||||
}
|
||||
resolved, err := buildCurrentDungeonAltarDifficultyResolvedBody(worldmap.BloodAltarDifficultyB)
|
||||
if err != nil || !bytes.Equal(resolved, []byte{2}) {
|
||||
t.Fatalf("op297 resolved=%x err=%v", resolved, err)
|
||||
}
|
||||
|
||||
ranking := buildCurrentDungeonAltarRankingBody(2, 3000, 4, 5000, 7, 6000)
|
||||
wantRanking := []uint32{3000, 2, 5000, 4, 7, 6000}
|
||||
if len(ranking) != 24 {
|
||||
t.Fatalf("op208 len=%d body=%x", len(ranking), ranking)
|
||||
}
|
||||
for index, want := range wantRanking {
|
||||
if got := binary.LittleEndian.Uint32(ranking[index*4 : index*4+4]); got != want {
|
||||
t.Fatalf("op208 field[%d]=%d want=%d body=%x", index, got, want, ranking)
|
||||
}
|
||||
}
|
||||
|
||||
reward, err := buildCurrentDungeonAltarRewardBody(3, 7, [4][]currentDungeonAltarRewardEntry{
|
||||
{{ItemID: 11, Amount: 2}}, nil, {{ItemID: 22, Amount: 3}}, nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantReward := []byte{
|
||||
3, 7,
|
||||
1, 11, 0, 0, 0, 2, 0, 0, 0,
|
||||
0,
|
||||
1, 22, 0, 0, 0, 3, 0, 0, 0,
|
||||
0,
|
||||
}
|
||||
if !bytes.Equal(reward, wantReward) {
|
||||
t.Fatalf("op209=%x want=%x", reward, wantReward)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCurrentDungeonAltarMonsterSpawnBodyWidth(t *testing.T) {
|
||||
wave := currentDungeonAltarWave{
|
||||
Spawns: []currentDungeonAltarSpawn{{
|
||||
Variant: 19, Sequence: 7, MonsterCode: 56017, MonsterType: 3,
|
||||
Level: 70, Scale: 1500, X: 440, Y: 222, Z: 0,
|
||||
}},
|
||||
Tail: 1,
|
||||
}
|
||||
body, err := buildCurrentDungeonAltarMonsterSpawnBody(wave)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(body) != 21 || binary.LittleEndian.Uint16(body[:2]) != 1 || body[2] != 19 ||
|
||||
binary.LittleEndian.Uint16(body[3:5]) != 7 || binary.LittleEndian.Uint32(body[5:9]) != 56017 ||
|
||||
body[9] != 3 || body[10] != 70 || binary.LittleEndian.Uint16(body[11:13]) != 1500 ||
|
||||
binary.LittleEndian.Uint16(body[13:15]) != 440 || binary.LittleEndian.Uint16(body[15:17]) != 222 ||
|
||||
binary.LittleEndian.Uint16(body[19:21]) != 1 {
|
||||
t.Fatalf("op210 body=%x", body)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user