Files
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

247 lines
9.0 KiB
Go

package dnfbridge
import (
"context"
"os"
"testing"
dnfmonster "longheng.io/server/internal/modules/dnf/monster"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
dnfquest "longheng.io/server/internal/modules/dnf/quest"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func TestCurrentQuestDropMatchMonsterIncludesTypeOneEnemyRewardItem(t *testing.T) {
ctx := context.Background()
source := bridgePVFSource{
dnfquest.DefaultList: "3233 `enemy_reward.qst`\n3234 `monster_reward.qst`\n",
"n_quest/enemy_reward.qst": "[grade]\n" +
"`[epic]`\n" +
"[type]\n" +
"`[seeking]`\n" +
"[int data]\n" +
"10164861 30\n" +
"[enemy reward item]\n" +
"420 1 32 -1 10164861 1 100 30\n" +
"421 1 32 2 10164861 2 50 30\n" +
"420 2 32 -1 10164862 1 100 30\n" +
"16111 3 102 -1 10164801 1 100 15\n" +
"[/enemy reward item]\n",
"n_quest/monster_reward.qst": "[grade]\n" +
"`[normal]`\n" +
"[type]\n" +
"`[seeking]`\n" +
"[int data]\n" +
"10164863 1\n" +
"[monster reward item]\n" +
"420 32 -1 10164863 1 75 1\n" +
"[/monster reward item]\n",
}
index, err := dnfpvf.Build(ctx, source, dnfpvf.BuildOptions{Lists: []string{dnfquest.DefaultList}})
if err != nil {
t.Fatal(err)
}
catalog, err := dnfquest.Load(ctx, index)
if err != nil {
t.Fatal(err)
}
candidates := currentQuestDropMatchMonster(catalog, []int64{3233, 3234}, 32, 0, 420)
if len(candidates) != 2 {
t.Fatalf("candidates=%+v", candidates)
}
if candidates[0] != (currentQuestDropCandidate{QuestID: 3233, ItemID: 10164861, Count: 1, DropRate: 100, MaxStack: 30}) {
t.Fatalf("enemy reward candidate=%+v", candidates[0])
}
if candidates[1] != (currentQuestDropCandidate{QuestID: 3234, ItemID: 10164863, Count: 1, DropRate: 75, MaxStack: 1}) {
t.Fatalf("monster reward candidate=%+v", candidates[1])
}
if got := currentQuestDropMatchMonster(catalog, []int64{3233}, 32, 0, 421); len(got) != 0 {
t.Fatalf("difficulty-specific enemy reward matched wrong difficulty: %+v", got)
}
if got := currentQuestDropMatchMonster(catalog, []int64{3233}, 32, 2, 421); len(got) != 1 || got[0].Count != 2 || got[0].DropRate != 50 {
t.Fatalf("difficulty-specific enemy reward=%+v", got)
}
if got := currentQuestDropMatchMonster(catalog, []int64{3233}, 31, 0, 420); len(got) != 0 {
t.Fatalf("enemy reward matched wrong dungeon: %+v", got)
}
if got := currentQuestDropMatchMonster(catalog, []int64{3233}, 102, 0, 16111); len(got) != 0 {
t.Fatalf("type-3 passive object leaked into monster matching: %+v", got)
}
got := currentQuestDropMatchEnemy(catalog, []int64{3233}, 102, 3, 16111, 3)
if len(got) != 1 || got[0] != (currentQuestDropCandidate{QuestID: 3233, ItemID: 10164801, Count: 1, DropRate: 100, MaxStack: 15}) {
t.Fatalf("type-3 passive-object reward=%+v", got)
}
if got := currentQuestDropMatchEnemy(catalog, []int64{3233}, 102, 3, 16111, 1); len(got) != 0 {
t.Fatalf("type-3 passive object leaked into type-1 matching: %+v", got)
}
if got := currentQuestDropMatchEnemy(catalog, []int64{3233}, 101, 3, 16111, 3); len(got) != 0 {
t.Fatalf("type-3 passive object matched wrong dungeon: %+v", got)
}
}
func TestCurrentQuestDropMatchImplicitMonsterUsesPVFScopeAndHiddenBossExplanation(t *testing.T) {
ctx := context.Background()
source := bridgePVFSource{
dnfquest.DefaultList: "8557 `event.qst`\n13701 `regular.qst`\n12872 `hidden.qst`\n",
"n_quest/event.qst": "[name]\n`进攻准备`\n[type]\n`[seeking]`\n[int data]\n10094602 10\n",
"n_quest/regular.qst": "[name]\n`进攻准备`\n[type]\n`[seeking]`\n[int data]\n10094602 3\n[dungeon info]\n3200 -1\n[/dungeon info]\n",
"n_quest/hidden.qst": "[name]\n`异界的异变 (2/4)`\n[type]\n`[seeking]`\n[int data]\n10000204 1\n",
}
index, err := dnfpvf.Build(ctx, source, dnfpvf.BuildOptions{Lists: []string{dnfquest.DefaultList}})
if err != nil {
t.Fatal(err)
}
questCatalog, err := dnfquest.Load(ctx, index)
if err != nil {
t.Fatal(err)
}
itemCatalog := &pvfDungeonDropCatalog{
source: bridgePVFSource{},
itemCache: map[uint32]dungeonDropItemDefinition{
10094602: {ItemID: 10094602, Kind: dungeonDropItemStackable, StackableType: "[material]"},
10000204: {ItemID: 10000204, Kind: dungeonDropItemStackable, StackableType: "[quest]", Explain: "消灭异次元裂缝的依普利乌斯时,可以获得该道具"},
},
}
monsterCatalog := &pvfDungeonMonsterCatalog{
source: bridgePVFSource{},
cache: map[int64]dnfmonster.Monster{
420: {ID: 420, Path: "monster/ordinary.mob", Name: "普通怪物"},
107000256: {
ID: 107000256, Path: "monster/newmonsters/dimensioncrack/specialboss/iflius/iflius1.mob",
Name: "依普利乌斯", Sections: []string{"dimension hidden boss"},
},
},
}
material := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{8557}, 3200, 0, 420,
)
if len(material) != 1 || material[0] != (currentQuestDropCandidate{
QuestID: 8557, ItemID: 10094602, Count: 1, DropRate: 100, MaxStack: 10,
}) {
t.Fatalf("scoped material candidate=%+v", material)
}
if got := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{8557}, 3201, 0, 420,
); len(got) != 0 {
t.Fatalf("scoped material leaked into wrong dungeon: %+v", got)
}
hiddenBoss := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{12872}, 7177, 3, 107000256,
)
if len(hiddenBoss) != 1 || hiddenBoss[0] != (currentQuestDropCandidate{
QuestID: 12872, ItemID: 10000204, Count: 1, DropRate: 100, MaxStack: 1,
}) {
t.Fatalf("hidden boss candidate=%+v", hiddenBoss)
}
if got := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{12872}, 7177, 3, 420,
); len(got) != 0 {
t.Fatalf("quest item leaked to ordinary monster: %+v", got)
}
}
func TestRealScriptPVFImplicitSeekingQuestDrops(t *testing.T) {
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify implicit seeking quest drops")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
index, err := dnfpvf.Build(context.Background(), archive, dnfpvf.BuildOptions{
Lists: []string{dnfquest.DefaultList}, Prefixes: []string{"n_quest/"},
})
if err != nil {
t.Fatal(err)
}
questCatalog, err := dnfquest.Load(context.Background(), index)
if err != nil {
t.Fatal(err)
}
scopes := questCatalog.SeekingDungeonScopes(8557)
if len(scopes) != 1 || scopes[0] != (dnfquest.DungeonScope{DungeonID: 3200, Difficulty: -1}) {
var related []dnfquest.Definition
for _, definition := range questCatalog.Definitions() {
for _, target := range questCatalog.SeekingItemTargets(definition.ID) {
if target.ItemID == 10094602 {
related = append(related, definition)
}
}
}
t.Fatalf("quest 8557 inherited scopes=%+v related=%+v", scopes, related)
}
itemCatalog, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatal(err)
}
monsterCatalog, err := newPVFDungeonMonsterCatalog(archive)
if err != nil {
t.Fatal(err)
}
material := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{8557}, 3200, 0, 107000256,
)
if len(material) != 1 || material[0].ItemID != 10094602 || material[0].MaxStack != 10 {
t.Fatalf("quest 8557 material candidate=%+v", material)
}
hiddenBoss := currentQuestDropMatchImplicitMonster(
questCatalog, itemCatalog, monsterCatalog, []int64{12872}, 7177, 3, 107000256,
)
if len(hiddenBoss) != 1 || hiddenBoss[0] != (currentQuestDropCandidate{
QuestID: 12872, ItemID: 10000204, Count: 1, DropRate: 100, MaxStack: 1,
}) {
t.Fatalf("quest 12872 hidden-boss candidate=%+v", hiddenBoss)
}
}
func TestCurrentQuestHelperDropRateRaisesOneFifthAndCaps(t *testing.T) {
tests := []struct {
base int64
active bool
want int64
}{
{base: 50, active: false, want: 50},
{base: 50, active: true, want: 60},
{base: 83, active: true, want: 100},
{base: 100, active: true, want: 100},
{base: 0, active: true, want: 0},
}
for _, test := range tests {
if got := currentQuestHelperDropRate(test.base, test.active); got != test.want {
t.Fatalf("base=%d active=%t got=%d want=%d", test.base, test.active, got, test.want)
}
}
}
func TestCurrentQuestItemDropRateCombinesGrowthContractAndQuestHelper(t *testing.T) {
tests := []struct {
base int64
growthPercent int64
questHelper bool
want int64
}{
{base: 50, growthPercent: 0, questHelper: false, want: 50},
{base: 50, growthPercent: 20, questHelper: false, want: 60},
{base: 50, growthPercent: 20, questHelper: true, want: 70},
{base: 83, growthPercent: 20, questHelper: false, want: 100},
{base: 99, growthPercent: -1, questHelper: false, want: 99},
{base: 0, growthPercent: 20, questHelper: true, want: 0},
}
for _, test := range tests {
if got := currentQuestItemDropRate(test.base, test.growthPercent, test.questHelper); got != test.want {
t.Fatalf(
"base=%d growth=%d helper=%t got=%d want=%d",
test.base,
test.growthPercent,
test.questHelper,
got,
test.want,
)
}
}
}