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

+125
View File
@@ -0,0 +1,125 @@
package services
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"pvfine/internal/pvf"
)
func newEditorSaveFixture(t *testing.T) (string, *core, int32, []byte) {
t.Helper()
built := pvf.New()
index, err := built.AddFileText("test/example.equ", "[name]\n`before`", pvf.TypeScript)
if err != nil {
t.Fatal(err)
}
var encoded bytes.Buffer
if err := built.SaveTo(&encoded); err != nil {
t.Fatal(err)
}
sourcePath := filepath.Join(t.TempDir(), "source.pvf")
original := encoded.Bytes()
if err := os.WriteFile(sourcePath, original, 0o644); err != nil {
t.Fatal(err)
}
archive, err := pvf.Open(sourcePath)
if err != nil {
t.Fatal(err)
}
core := NewCore()
if err := core.setArchive(archive); err != nil {
t.Fatal(err)
}
t.Cleanup(core.closeArchive)
return sourcePath, core, index, append([]byte(nil), original...)
}
func TestEditorServiceSaveBacksUpSourceByDefault(t *testing.T) {
sourcePath, core, index, original := newEditorSaveFixture(t)
editor := NewEditorService(core)
if err := editor.SetText(index, "[name]\n`after`\n"); err != nil {
t.Fatal(err)
}
if _, err := editor.Save(); err != nil {
t.Fatal(err)
}
backup, err := os.ReadFile(sourcePath + ".bak")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(backup, original) {
t.Fatal("source backup does not contain the original archive")
}
saved, err := pvf.Open(sourcePath)
if err != nil {
t.Fatal(err)
}
savedIndex, ok := saved.Find("test/example.equ")
if !ok {
t.Fatal("saved file is missing")
}
text, err := saved.Text(savedIndex)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(text, "after") {
t.Fatalf("saved text = %q", text)
}
}
func TestEditorServiceSaveCanDisableSourceBackup(t *testing.T) {
sourcePath, core, index, _ := newEditorSaveFixture(t)
settingsService := newSettingsService(filepath.Join(t.TempDir(), "settings.json"))
settings := DefaultAppSettings()
settings.BackupSourceOnSave = false
if err := settingsService.SaveSettings(settings); err != nil {
t.Fatal(err)
}
editor := NewEditorService(core, settingsService)
if err := editor.SetText(index, "[name]\n`after`\n"); err != nil {
t.Fatal(err)
}
if _, err := editor.Save(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(sourcePath + ".bak"); !os.IsNotExist(err) {
t.Fatalf("source backup exists or stat failed: %v", err)
}
}
func TestBackupSourceProgressCountsBytes(t *testing.T) {
sourcePath := filepath.Join(t.TempDir(), "source.pvf")
original := bytes.Repeat([]byte("backup data"), 300000)
if err := os.WriteFile(sourcePath, original, 0o600); err != nil {
t.Fatal(err)
}
var lastCount int64
var synced bool
if err := backupSourceFileWithProgress(sourcePath, func(p pvf.SaveProgress) {
if p.Phase == "backup" {
if p.Total != int64(len(original)) || p.Done < lastCount || p.Done > p.Total {
t.Fatalf("invalid backup progress: %+v", p)
}
lastCount = p.Done
} else if p.Phase == "backup-sync" {
if lastCount != int64(len(original)) {
t.Fatal("sync preceded full copy")
}
synced = true
}
}); err != nil {
t.Fatal(err)
}
backup, err := os.ReadFile(sourcePath + ".bak")
if err != nil || !bytes.Equal(backup, original) || !synced {
t.Fatal("backup progress changed backup content or omitted sync")
}
}