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

167 lines
4.5 KiB
Go

// Scratch tool: scan every .qst in a Script.pvf for reward declarations that
// reference one of the account-shared materials (cubes 3033..3037 / 3262,
// souls 10100115 / 10100116 / 10099773..10099775 / 10158124).
//
// go run ./cmd/scratch/questscan -pvf <Script.pvf>
//
// It reports, per section kind:
// - monster/enemy reward rows (fixed-width tuples, item id at a known index)
// - item/count pair sections ([reward int data], [reward selection int data],
// [clear reward item], [sub reward]) by locating the id in the token stream
//
// Purpose: find every quest-side acquisition path that can hand out a cube or
// soul, and read the declared max_stack for the drop-roll rows.
package main
import (
"flag"
"fmt"
"os"
"sort"
"strings"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
platformpvf "longheng.io/server/internal/platform/pvf"
)
var accountSharedIDs = map[int64]string{
3033: "3033", 3034: "3034", 3035: "3035", 3036: "3036",
3037: "3037 无色小晶块", 3262: "3262",
10100115: "10100115", 10100116: "10100116",
10099773: "10099773", 10099774: "10099774", 10099775: "10099775",
10158124: "10158124",
}
// pairSections are item/count (or item/count/job) declarations.
var pairSections = []string{
"reward int data",
"reward selection int data",
"clear reward item",
"sub reward",
}
func main() {
pvfPath := flag.String("pvf", "../runtime/data/dnf/Script.pvf", "Script.pvf path")
flag.Parse()
archive, err := platformpvf.Open(*pvfPath)
if err != nil {
fmt.Fprintln(os.Stderr, "open:", err)
os.Exit(1)
}
var (
scanned int
dropRows []string
pairHits []string
maxStacks = map[int64]int{}
pairCount = map[string]int{}
)
for _, file := range archive.Files() {
full := strings.ReplaceAll(file.ArchivePath, "\\", "/")
if strings.TrimSpace(full) == "" {
full = strings.ReplaceAll(file.Path, "\\", "/")
}
if !strings.HasSuffix(strings.ToLower(full), ".qst") {
continue
}
text, readErr := archive.ReadText(full)
if readErr != nil {
continue
}
scanned++
doc, parseErr := dnfpvf.Parse(full, text)
if parseErr != nil {
continue
}
for _, section := range []string{"enemy reward item", "monster reward item"} {
tokens, ok := doc.Section(section)
if !ok {
continue
}
width := 8
if section == "monster reward item" {
width = 7
}
ints := sectionInts(tokens)
for i := 0; i+width-1 < len(ints); i += width {
itemIndex := 4
if section == "monster reward item" {
itemIndex = 3
}
itemID := ints[i+itemIndex]
if _, ok := accountSharedIDs[itemID]; !ok {
continue
}
maxStacks[ints[i+width-1]]++
dropRows = append(dropRows, fmt.Sprintf("%s [%s] %v item=%d max_stack=%d",
full, section, ints[i:i+width], itemID, ints[i+width-1]))
}
}
for _, section := range pairSections {
tokens, ok := doc.Section(section)
if !ok {
continue
}
ints := sectionInts(tokens)
for index, value := range ints {
name, ok := accountSharedIDs[value]
if !ok {
continue
}
// 打印 id 前后的 token,看清是 (id,count) 还是别的形状。
low := index - 2
if low < 0 {
low = 0
}
high := index + 3
if high > len(ints) {
high = len(ints)
}
pairCount[section]++
pairHits = append(pairHits, fmt.Sprintf("%s [%s] idx=%d window=%v (%s)",
full, section, index, ints[low:high], name))
}
}
}
sort.Strings(dropRows)
sort.Strings(pairHits)
fmt.Printf("scanned .qst=%d\n", scanned)
fmt.Printf("--- drop-roll rows (enemy/monster reward item) ---\n")
for _, line := range dropRows {
fmt.Println(line)
}
fmt.Println("--- max_stack histogram (drop-roll rows) ---")
stacks := make([]int64, 0, len(maxStacks))
for stack := range maxStacks {
stacks = append(stacks, stack)
}
sort.Slice(stacks, func(a, b int) bool { return stacks[a] < stacks[b] })
for _, stack := range stacks {
fmt.Printf("max_stack=%d rows=%d\n", stack, maxStacks[stack])
}
fmt.Printf("--- item/count section hits ---\n")
for _, line := range pairHits {
fmt.Println(line)
}
fmt.Println("--- hits per section ---")
names := make([]string, 0, len(pairCount))
for name := range pairCount {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s hits=%d\n", name, pairCount[name])
}
}
func sectionInts(tokens []dnfpvf.Token) []int64 {
ints := make([]int64, 0, len(tokens))
for _, token := range tokens {
if token.Kind == dnfpvf.TokenInt {
ints = append(ints, token.Int)
}
}
return ints
}