DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,184 @@
|
||||
// questgolds 只读扫描 Script.pvf 里的任务定义,统计"哪些任务要求交付金币"。
|
||||
//
|
||||
// 现场 2026-10-03(服主报):所有消耗金币的任务都不扣钱。开耳环槽位的任务 [int data] 是
|
||||
//
|
||||
// 0 1000000 3034 1000 3033 1000 3035 1000 3036 1000 10158964 5
|
||||
//
|
||||
// 服主给定口径:**[int data] 是 (ID, 数量) 成对,ID 0 = 金币。**
|
||||
// 服务端把 itemID <= 0 的 pair 当非法数据跳过了,所以 (0, 1000000) 整条被丢掉 —— 金币不扣。
|
||||
//
|
||||
// 本工具用来量范围:全库有多少任务在交付里要金币、合计多少。
|
||||
// 注意 quest.lst 里不少条目指向的文档在本 PVF 中并不存在(例如 2636 →
|
||||
// new_quest/MetroCenter/system_90_earring_02.qst),所以按**实际存在的 .qst 文档**扫。
|
||||
//
|
||||
// 只读。跑法:
|
||||
//
|
||||
// go run ./cmd/scratch/questgolds -pvf "D:\...\runtime\data\dnf\Script.pvf"
|
||||
// go run ./cmd/scratch/questgolds -pvf ... -file "n_quest/....qst" # 打印单个文档
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pvfPath := flag.String("pvf", "", "Script.pvf (required)")
|
||||
questFile := flag.String("file", "", "optional: dump one .qst document verbatim")
|
||||
verbose := flag.Bool("v", false, "also print the full [int data] of every gold quest")
|
||||
limit := flag.Int("limit", 40, "max rows to print")
|
||||
flag.Parse()
|
||||
if *pvfPath == "" {
|
||||
fmt.Fprintln(os.Stderr, "FAILED: -pvf is required")
|
||||
os.Exit(2)
|
||||
}
|
||||
archive, err := platformpvf.Open(*pvfPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FAILED: open pvf:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *questFile != "" {
|
||||
text, readErr := archive.ReadText(*questFile)
|
||||
if readErr != nil {
|
||||
fmt.Fprintln(os.Stderr, "FAILED: read:", readErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("=== %s ===\n%s\n", *questFile, text)
|
||||
return
|
||||
}
|
||||
|
||||
index, err := dnfpvf.Build(context.Background(), archive, dnfpvf.BuildOptions{
|
||||
Lists: []string{"n_quest/quest.lst"},
|
||||
Prefixes: []string{"n_quest/", "quest/"},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FAILED: build index:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
paths := make([]string, 0, 16384)
|
||||
for _, path := range index.Paths() {
|
||||
if strings.HasSuffix(strings.ToLower(path), ".qst") {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
sort.Strings(paths)
|
||||
fmt.Printf("扫描 .qst 文档=%d\n", len(paths))
|
||||
|
||||
type hit struct {
|
||||
path string
|
||||
kind string
|
||||
source string
|
||||
values []int64
|
||||
}
|
||||
hits := make([]hit, 0, 256)
|
||||
goldOnly := 0
|
||||
var goldTotal, goldMax int64
|
||||
byType := map[string]int{}
|
||||
goldPair := func(values []int64) bool {
|
||||
for index := 0; index+1 < len(values); index += 2 {
|
||||
if values[index] == 0 && values[index+1] > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
goldSum := func(values []int64) int64 {
|
||||
total := int64(0)
|
||||
for index := 0; index+1 < len(values); index += 2 {
|
||||
if values[index] == 0 && values[index+1] > 0 {
|
||||
total += values[index+1]
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
for _, path := range paths {
|
||||
text, readErr := archive.ReadText(path)
|
||||
if readErr != nil {
|
||||
continue
|
||||
}
|
||||
doc, parseErr := dnfpvf.Parse(path, text)
|
||||
if parseErr != nil {
|
||||
continue
|
||||
}
|
||||
kind, _ := doc.Text("type")
|
||||
kind = strings.TrimSpace(kind)
|
||||
intData := doc.Ints("int data")
|
||||
dependData := doc.Ints("depend give item")
|
||||
for _, section := range []struct {
|
||||
name string
|
||||
values []int64
|
||||
}{
|
||||
{name: "[int data]", values: intData},
|
||||
{name: "[depend give item]", values: dependData},
|
||||
} {
|
||||
if !goldPair(section.values) {
|
||||
continue
|
||||
}
|
||||
hits = append(hits, hit{path: path, kind: kind, source: section.name, values: section.values})
|
||||
if section.name == "[int data]" {
|
||||
byType[kind]++
|
||||
}
|
||||
goldOnly++
|
||||
goldTotal += goldSum(section.values)
|
||||
if amount := goldSum(section.values); amount > goldMax {
|
||||
goldMax = amount
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("交付段里含 (0, 金额) 金币消耗的任务文档=%d\n", len(hits))
|
||||
fmt.Printf("金币总额=%d,单笔最大=%d\n", goldTotal, goldMax)
|
||||
types := make([]string, 0, len(byType))
|
||||
for name := range byType {
|
||||
types = append(types, name)
|
||||
}
|
||||
sort.Slice(types, func(i, j int) bool { return byType[types[i]] > byType[types[j]] })
|
||||
bySource := map[string]int{}
|
||||
for _, item := range hits {
|
||||
bySource[item.source]++
|
||||
}
|
||||
fmt.Printf("\n按所在段分布(决定语义的是这里):\n")
|
||||
sources := make([]string, 0, len(bySource))
|
||||
for name := range bySource {
|
||||
sources = append(sources, name)
|
||||
}
|
||||
sort.Strings(sources)
|
||||
for _, name := range sources {
|
||||
fmt.Printf(" %-22s %d\n", name, bySource[name])
|
||||
}
|
||||
fmt.Printf("\n按 [type] 分布(仅 [int data]):\n")
|
||||
for _, name := range types {
|
||||
fmt.Printf(" %-24s %d\n", name, byType[name])
|
||||
}
|
||||
fmt.Printf("\n明细(前 %d 条):\n", *limit)
|
||||
for index, item := range hits {
|
||||
if index >= *limit {
|
||||
fmt.Printf(" ...(还有 %d 条)\n", len(hits)-index)
|
||||
break
|
||||
}
|
||||
fmt.Printf(" %-20s type=%-22s %s\n", item.source, item.kind, item.path)
|
||||
if *verbose {
|
||||
fmt.Printf(" [int data]=%v\n", item.values)
|
||||
}
|
||||
}
|
||||
if goldOnly > 0 {
|
||||
fmt.Printf("\n纯金币交付样例:\n")
|
||||
shown := 0
|
||||
for _, item := range hits {
|
||||
if len(item.values) != 2 {
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" 金币 %-10d %s\n", item.values[1], item.path)
|
||||
shown++
|
||||
if shown >= 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user