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

114 lines
4.0 KiB
Go

package dnfbridge
import (
"testing"
"time"
dnfdungeonadmission "longheng.io/server/internal/modules/dnf/dungeonadmission"
)
// 验证周常五图的准入策略生成与开放日开关。
func TestAbisEntryPolicies(t *testing.T) {
for _, spec := range abisDungeonSpecs {
policies, err := abisEntryPolicies(spec, 0)
if err != nil {
t.Fatalf("dungeon %d: %v", spec.id, err)
}
if len(policies) != 2 {
t.Fatalf("dungeon %d: want 2 policies, got %d", spec.id, len(policies))
}
account, character := policies[0], policies[1]
if account.Scope == character.Scope {
t.Fatalf("dungeon %d: both policies share scope %d", spec.id, account.Scope)
}
t.Logf("dungeon %d %s: account key=%s limit=%d section=%q | character key=%s limit=%d section=%q",
spec.id, spec.name,
account.Key, account.Limit, account.SourceSection,
character.Key, character.Limit, character.SourceSection)
if account.Key != abisSharedAccountPolicyKey {
t.Fatalf("dungeon %d: account policy key=%q want=%q (must be shared across all five)", spec.id, account.Key, abisSharedAccountPolicyKey)
}
if len(account.DungeonIDs) != 5 {
t.Fatalf("dungeon %d: shared account policy must cover 5 dungeons, got %v", spec.id, account.DungeonIDs)
}
if account.Limit != abisAccountDailyBase {
t.Fatalf("dungeon %d: account limit=%d want=%d", spec.id, account.Limit, abisAccountDailyBase)
}
if character.Limit != spec.limit {
t.Fatalf("dungeon %d: character limit=%d want=%d", spec.id, character.Limit, spec.limit)
}
}
}
// 冒险团分档加成必须落在 PVF 声明的区间上。
func TestAbisAdventureGroupAddition(t *testing.T) {
cases := []struct {
level uint32
want uint32
}{
{0, 0}, {1, 0}, {5, 0}, {6, 1}, {10, 1}, {11, 2}, {15, 2},
{16, 3}, {20, 3}, {21, 4}, {25, 4}, {26, 5}, {30, 5}, {31, 0},
}
for _, c := range cases {
if got := abisAdventureGroupAddition(c.level); got != c.want {
t.Errorf("manage level %d: bonus=%d want=%d", c.level, got, c.want)
}
}
}
// 06:00 服务日边界:北京 05:59 属于前一天,06:00 属于新一天。
func TestAbisServiceWindowSixAMBoundary(t *testing.T) {
beijing := time.FixedZone("CST", 8*3600)
before := time.Date(2026, 10, 12, 5, 59, 0, 0, beijing)
after := time.Date(2026, 10, 12, 6, 0, 0, 0, beijing)
policy := abisEntryPoliciesForWindowProbe()
w1, err := abisPolicyWindow(policy, before)
if err != nil {
t.Fatal(err)
}
w2, err := abisPolicyWindow(policy, after)
if err != nil {
t.Fatal(err)
}
if w1.Start.Equal(w2.Start) {
t.Fatalf("05:59 and 06:00 must be different service days, both start=%v", w1.Start)
}
t.Logf("05:59 -> window %v ; 06:00 -> window %v", w1.Start, w2.Start)
if w1.Start.Hour() != 6 || w2.Start.Hour() != 6 {
t.Fatalf("service day must start at 06:00, got %d and %d", w1.Start.Hour(), w2.Start.Hour())
}
}
func abisEntryPoliciesForWindowProbe() dnfdungeonadmission.Policy {
policies, _ := abisEntryPolicies(abisDungeonSpecs[0], 0)
return policies[0]
}
// 冒险团分档加成必须计入账号策略上限:账号额度 = 5 + 分档加成。
func TestAbisAccountPolicyIncludesAdventureGroupBonus(t *testing.T) {
cases := []struct {
manageLevel uint32
wantLimit uint32
}{
{0, 5}, {1, 5}, {5, 5}, {6, 6}, {10, 6}, {11, 7}, {16, 8},
{21, 9}, {26, 10}, {30, 10}, {31, 5},
}
for _, c := range cases {
policies, err := abisEntryPolicies(abisDungeonSpecs[0], c.manageLevel)
if err != nil {
t.Fatalf("manage level %d: %v", c.manageLevel, err)
}
if got := policies[0].Limit; got != c.wantLimit {
t.Errorf("manage level %d: account limit=%d want=%d", c.manageLevel, got, c.wantLimit)
}
// 共享键必须与等级无关,否则同一账号不同角色会各生成一条账号策略。
if policies[0].Key != abisSharedAccountPolicyKey {
t.Errorf("manage level %d: account key=%q want shared", c.manageLevel, policies[0].Key)
}
// 角色上限不受冒险团等级影响。
if got := policies[1].Limit; got != abisDungeonSpecs[0].limit {
t.Errorf("manage level %d: character limit=%d want=%d", c.manageLevel, got, abisDungeonSpecs[0].limit)
}
}
}