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,145 @@
// avatarcompoundcheck 用来核对时装合成器的 PVF 数据。
//
// go run ./cmd/scratch/avatarcompoundcheck scan 列出每个职业分表的稀有池/普通池及各候选自己的 grade
// go run ./cmd/scratch/avatarcompoundcheck 112510093 ... 打印指定装备的 name/grade/rarity/equipment type
//
// scan 的结论:16 张分表 586 个稀有候选的 PVF grade 全是 3,普通候选全是 2,
// 也就是分表里的 [grade] 1 与 [upper grade] 2 说的是**投入的来源装扮**,
// 不是产出物品的档次。
package main
import (
"flag"
"fmt"
"path"
"strings"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func resolvePath(archive *platformpvf.Archive, listPath, reference string) string {
reference = strings.TrimSpace(strings.Trim(reference, "`\"'"))
for _, candidate := range []string{reference, path.Join(path.Dir(listPath), reference)} {
if file, ok := archive.FindFile(candidate); ok {
return file.ArchivePath
}
}
return ""
}
func main() {
pvfPath := flag.String("pvf", `../runtime/data/dnf/Script.pvf`, "Script.pvf path")
flag.Parse()
archive, err := platformpvf.Open(*pvfPath)
if err != nil {
panic(err)
}
text, err := archive.ReadText("equipment/equipment.lst")
if err != nil {
panic(err)
}
doc, err := dnfpvf.Parse("equipment/equipment.lst", text)
if err != nil {
panic(err)
}
byID := map[int64]string{}
for _, entry := range dnfpvf.ParseList(doc) {
byID[entry.ID] = resolvePath(archive, "equipment/equipment.lst", entry.Path)
}
grade := func(id int64) int64 {
p := byID[id]
if p == "" {
return -1
}
txt, e := archive.ReadText(p)
if e != nil {
return -1
}
d, e := dnfpvf.Parse(p, txt)
if e != nil {
return -1
}
g, _ := d.Int("grade")
return g
}
if len(flag.Args()) == 1 && flag.Args()[0] == "scan" {
idxText, _ := archive.ReadText("etc/chn_compound/compoundavatar.etc")
idxDoc, _ := dnfpvf.Parse("etc/chn_compound/compoundavatar.etc", idxText)
seen := map[int64]int{}
for _, tok := range idxDoc.Tokens {
name := strings.TrimSpace(strings.Trim(tok.Value, "`"))
if !strings.HasSuffix(strings.ToLower(name), ".etc") {
continue
}
tp := "etc/chn_compound/" + strings.ToLower(name)
tt, readErr := archive.ReadText(tp)
if readErr != nil {
fmt.Println(tp, "ERR", readErr)
continue
}
td, parseErr := dnfpvf.Parse(tp, tt)
if parseErr != nil {
continue
}
g, _ := td.Int("grade")
ug, _ := td.Int("upper grade")
fmt.Printf("== %s grade=%d upper=%d\n", name, g, ug)
for _, part := range []string{"hat", "hair", "face", "neck", "coat", "pants", "belt", "shoes"} {
vals := td.Ints(part + " avatar")
if len(vals) < 3 {
continue
}
n := int(vals[0])
pairs := vals[1:]
specials := ""
for i := 0; i*2+1 < len(pairs) && i < n; i++ {
gr := grade(pairs[i*2])
specials += fmt.Sprintf(" %d(w=%d,g=%d)", pairs[i*2], pairs[i*2+1], gr)
seen[gr]++
}
normals := ""
for i := n; i*2+1 < len(pairs); i++ {
normals += fmt.Sprintf(" %d(w=%d,g=%d)", pairs[i*2], pairs[i*2+1], grade(pairs[i*2]))
}
fmt.Printf(" %-6s specials:%s | normals:%s\n", part, specials, normals)
}
}
fmt.Println("special grade histogram:", seen)
return
}
for _, id := range flag.Args() {
var itemID int64
fmt.Sscan(id, &itemID)
p := byID[itemID]
if p == "" {
fmt.Printf("%d: NOT IN equipment.lst\n", itemID)
continue
}
t, readErr := archive.ReadText(p)
if readErr != nil {
fmt.Printf("%d %s: read error %v\n", itemID, p, readErr)
continue
}
d, parseErr := dnfpvf.Parse(p, t)
if parseErr != nil {
fmt.Printf("%d %s: parse error %v\n", itemID, p, parseErr)
continue
}
g, hasGrade := d.Int("grade")
rarity, hasRarity := d.Int("rarity")
name := ""
if tokens, ok := d.Section("name"); ok && len(tokens) > 0 {
name = tokens[0].Value
}
eqType := ""
if tokens, ok := d.Section("equipment type"); ok && len(tokens) > 0 {
eqType = tokens[0].Value
}
fmt.Printf("%d %s name=%q grade=%d(%v) rarity=%d(%v) type=%s\n",
itemID, p, name, g, hasGrade, rarity, hasRarity, eqType)
}
}