DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

@@ -0,0 +1,259 @@
package dnfbridge
import (
"os"
"testing"
"longheng.io/server/internal/modules/dnf/alignedcmd"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func newUpgradeTicketTestCatalog(t *testing.T) (*pvfDungeonDropCatalog, dungeonDropCatalogTestSource) {
t.Helper()
source := dungeonDropCatalogTestSource{
"monster/monster.lst": "",
"stackable/stackable.lst": "9100 `ticket/plus10.stk`\n9101 `ticket/amplify12.stk`\n9102 `ticket/random.stk`\n9103 `ticket/plain.stk`\n9104 `ticket/portable_amplify.stk`\n9105 `ticket/portable_upgrade.stk`\n",
"equipment/equipment.lst": "700 `weapon/test_sword.equ`\n701 `weapon/sealed.equ`\n",
"stackable/ticket/plus10.stk": "[name] `+10 Ticket`\n[stackable type] `[material]`\n[equipment reinforcement ticket] 10 100\n",
"stackable/ticket/amplify12.stk": "[name] `+12 Amplify Ticket`\n[stackable type] `[material]`\n[equipment amplify reinforcement ticket] 12 50\n",
"stackable/ticket/random.stk": "[name] `Random Ticket`\n[stackable type] `[material]`\n[enchant random]\n[er_grade]\n0 1 2 3 5 6\n[er_slot]\n1\n[er_seal]\n2\n[er_enchant]\n2 10 60000 12 40000\n[/er_enchant]\n[/enchant random]\n",
"stackable/ticket/plain.stk": "[name] `Plain Material`\n[stackable type] `[material]`\n",
"stackable/ticket/portable_amplify.stk": "[name] `Portable Amplifier`\n[stackable type] `[waste]`\n[action type] `[portable amplify]` 0 50\n[/action type]\n[check usable itemlevel] 1 85\n",
"stackable/ticket/portable_upgrade.stk": "[name] `Portable Reinforcer`\n[stackable type] `[town and dungeon]`\n[action type] `[portable upgrade]` 10 30\n[/action type]\n[check usable itemlevel] 85 90\n",
"equipment/weapon/test_sword.equ": "[name] `Test Sword`\n[equipment type] `[weapon]`\n[rarity] 3\n[minimum level] 85\n[durability] 57\n",
"equipment/weapon/sealed.equ": "[name] `Sealed`\n[equipment type] `[weapon]`\n[impossible contents] `[upgrade]`\n",
}
catalog, err := newPVFDungeonDropCatalog(source)
if err != nil {
t.Fatal(err)
}
return catalog, source
}
func TestResolveCurrentUpgradeTicketMetadata(t *testing.T) {
catalog, source := newUpgradeTicketTestCatalog(t)
resolution, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9100, 700)
if err != nil {
t.Fatalf("resolve error = %v", err)
}
if resolution.TicketMode != "reinforce" || resolution.TargetLevel != 10 || resolution.SuccessWeight != 100000 {
t.Fatalf("reinforce ticket = %+v", resolution)
}
if resolution.TicketRandom {
t.Fatalf("regular ticket marked random")
}
if resolution.TargetKind != "equipment" || resolution.TargetEquipmentType != "[weapon]" || resolution.TargetUpgradeForbidden {
t.Fatalf("target = %+v", resolution)
}
if resolution.TargetRarity != 3 {
t.Fatalf("target rarity = %d, want 3", resolution.TargetRarity)
}
amplify, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9101, 700)
if err != nil {
t.Fatalf("resolve error = %v", err)
}
if amplify.TicketMode != "amplify" || amplify.TargetLevel != 12 || amplify.SuccessWeight != 50000 {
t.Fatalf("amplify ticket = %+v", amplify)
}
random, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9102, 700)
if err != nil {
t.Fatalf("resolve error = %v", err)
}
if !random.TicketRandom || random.TicketMode != "reinforce" {
t.Fatalf("random ticket = %+v", random)
}
if random.RandomTicket.SlotRestriction != 1 || random.RandomTicket.SealRestriction != 2 {
t.Fatalf("random restrictions = %+v", random.RandomTicket)
}
if len(random.RandomTicket.Rarities) != 6 || random.RandomTicket.Rarities[0] != 0 || random.RandomTicket.Rarities[5] != 6 {
t.Fatalf("random rarities = %+v", random.RandomTicket.Rarities)
}
wantEntries := []alignedcmd.UpgradeTicketRandomEntry{{TargetLevel: 10, Weight: 60000}, {TargetLevel: 12, Weight: 40000}}
if len(random.RandomTicket.Entries) != len(wantEntries) {
t.Fatalf("random entries = %+v, want leading count skipped", random.RandomTicket.Entries)
}
for index, want := range wantEntries {
if random.RandomTicket.Entries[index] != want {
t.Fatalf("random entry %d = %+v, want %+v", index, random.RandomTicket.Entries[index], want)
}
}
forbidden, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9100, 701)
if err != nil {
t.Fatalf("resolve error = %v", err)
}
if !forbidden.TargetUpgradeForbidden {
t.Fatalf("forbidden target not flagged: %+v", forbidden)
}
portable, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9104, 700)
if err != nil {
t.Fatal(err)
}
if !portable.PortableAmplifier || portable.TicketMode != "" || portable.PortableMinimumLevel != 0 || portable.PortableMaximumLevel != 50 ||
portable.TargetMinimumLevel != 85 || portable.TargetLevelMinimum != 1 || portable.TargetLevelMaximum != 85 || portable.TargetKind != "equipment" {
t.Fatalf("portable amplifier = %+v", portable)
}
reinforcer, err := resolveCurrentUpgradeTicketMetadata(catalog, source, 9105, 700)
if err != nil {
t.Fatal(err)
}
if !reinforcer.PortableReinforcer || reinforcer.TicketMode != "" || reinforcer.PortableMinimumLevel != 10 || reinforcer.PortableMaximumLevel != 30 ||
reinforcer.TargetMinimumLevel != 85 || reinforcer.TargetLevelMinimum != 85 || reinforcer.TargetLevelMaximum != 90 || reinforcer.TargetKind != "equipment" {
t.Fatalf("portable reinforcer = %+v", reinforcer)
}
}
func TestResolveCurrentUpgradeTicketMetadataNonTickets(t *testing.T) {
catalog, source := newUpgradeTicketTestCatalog(t)
tests := []struct {
name string
material int64
}{
{name: "plain stackable", material: 9103},
{name: "unknown material", material: 9999},
{name: "equipment as material", material: 700},
{name: "zero material", material: 0},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resolution, err := resolveCurrentUpgradeTicketMetadata(catalog, source, tc.material, 700)
if err != nil {
t.Fatalf("resolve error = %v", err)
}
if resolution.TicketMode != "" {
t.Fatalf("ticket mode = %q, want empty for non-ticket", resolution.TicketMode)
}
})
}
}
// TestRealPVFBlessedEnchantRandomTickets pins the shipped definitions of
// 祝福强化券 (武器) 2681908 and 祝福强化券 (防具) 2681909: both carry an
// [enchant random] block with the same eight er_enchant candidates and differ
// only in er_slot (1 weapon vs 2 armor).
func TestRealPVFBlessedEnchantRandomTickets(t *testing.T) {
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify the blessed enchant tickets")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
catalog, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatal(err)
}
wantEntries := []alignedcmd.UpgradeTicketRandomEntry{
{TargetLevel: 6, Weight: 15285},
{TargetLevel: 7, Weight: 30805},
{TargetLevel: 8, Weight: 32150},
{TargetLevel: 9, Weight: 11201},
{TargetLevel: 10, Weight: 8600},
{TargetLevel: 11, Weight: 1440},
{TargetLevel: 12, Weight: 437},
{TargetLevel: 13, Weight: 82},
}
for _, test := range []struct {
itemID int64
wantSlot int64
}{
{itemID: 2681908, wantSlot: 1},
{itemID: 2681909, wantSlot: 2},
} {
got, resolveErr := resolveCurrentUpgradeTicketMetadata(catalog, archive, test.itemID, 100240061)
if resolveErr != nil {
t.Fatalf("item=%d: %v", test.itemID, resolveErr)
}
if !got.TicketRandom || got.TicketMode != "reinforce" {
t.Fatalf("item=%d resolution=%+v, want a reinforce random ticket", test.itemID, got)
}
if got.RandomTicket.SlotRestriction != test.wantSlot || got.RandomTicket.SealRestriction != 2 ||
len(got.RandomTicket.Rarities) != 6 {
t.Fatalf("item=%d restrictions=%+v", test.itemID, got.RandomTicket)
}
if len(got.RandomTicket.Entries) != len(wantEntries) {
t.Fatalf("item=%d entries=%+v", test.itemID, got.RandomTicket.Entries)
}
for index, want := range wantEntries {
if got.RandomTicket.Entries[index] != want {
t.Fatalf("item=%d entry %d = %+v, want %+v", test.itemID, index, got.RandomTicket.Entries[index], want)
}
}
}
}
func TestRealPVFPortableAmplifiersUseTheirOwnMaterialContract(t *testing.T) {
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify the portable amplifier")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
catalog, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
itemID int64
currentMax int64
targetMinimum int64
targetMaximum int64
}{
{itemID: 10005313, currentMax: 50, targetMinimum: 1, targetMaximum: 85},
{itemID: 490702022, currentMax: 50, targetMinimum: 55, targetMaximum: 90},
{itemID: 490702253, currentMax: 30, targetMinimum: 55, targetMaximum: 90},
{itemID: 490702334, currentMax: 30, targetMinimum: 55, targetMaximum: 90},
} {
got, resolveErr := resolveCurrentUpgradeTicketMetadata(catalog, archive, test.itemID, 100240061)
if resolveErr != nil {
t.Fatalf("item=%d: %v", test.itemID, resolveErr)
}
if !got.PortableAmplifier || got.PortableMinimumLevel != 0 || got.PortableMaximumLevel != test.currentMax ||
got.TargetLevelMinimum != test.targetMinimum || got.TargetLevelMaximum != test.targetMaximum || got.TargetMinimumLevel != 85 ||
got.TargetKind != "equipment" {
t.Fatalf("portable amplifier item=%d resolution=%+v", test.itemID, got)
}
}
}
func TestRealPVFPortableReinforcersUseTheirOwnMaterialContract(t *testing.T) {
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify portable reinforcers")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
catalog, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
itemID int64
minimumLevel int64
maximumLevel int64
targetMin int64
targetMax int64
}{
{itemID: 490000362, minimumLevel: 0, maximumLevel: 9, targetMin: 1, targetMax: 86},
{itemID: 490702163, minimumLevel: 10, maximumLevel: 30, targetMin: 85, targetMax: 90},
} {
got, resolveErr := resolveCurrentUpgradeTicketMetadata(catalog, archive, test.itemID, 100240061)
if resolveErr != nil {
t.Fatalf("item=%d: %v", test.itemID, resolveErr)
}
if !got.PortableReinforcer || got.PortableMinimumLevel != test.minimumLevel || got.PortableMaximumLevel != test.maximumLevel ||
got.TargetLevelMinimum != test.targetMin || got.TargetLevelMaximum != test.targetMax || got.TargetKind != "equipment" {
t.Fatalf("portable reinforcer item=%d resolution=%+v", test.itemID, got)
}
}
}