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,147 @@
|
||||
// pvfnpc 在 .twn 指向的地图文件里查找 [NPC] 段中的指定 NPC id。
|
||||
//
|
||||
// 背景:建会 NPC「博肯」= npc/npc.lst 里的 15 `Board.npc`。
|
||||
// 地图 [NPC] 段记录形如:
|
||||
//
|
||||
// 1 `[left]` 1227 164 0 592 `[left]` 351 192 0
|
||||
// 16 800 400 0
|
||||
//
|
||||
// 即 "id [方向] x y z" 的可重复序列(方向可省)。这里按字段扫描。
|
||||
//
|
||||
// go run ./cmd/scratch/pvfnpc -pvf <path> -npc 15
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
var (
|
||||
areaRe = regexp.MustCompile("`([^`]+\\.map)`")
|
||||
intRe = regexp.MustCompile(`\d+`)
|
||||
quoteRe = regexp.MustCompile("`\\[[^`]*\\]`")
|
||||
)
|
||||
|
||||
func main() {
|
||||
pvfPath := flag.String("pvf", `../runtime/data/dnf/Script.pvf`, "Script.pvf path")
|
||||
npcID := flag.Int64("npc", 15, "要查找的 NPC id")
|
||||
verbose := flag.Bool("v", false, "打印每个城镇扫描了多少地图")
|
||||
flag.Parse()
|
||||
|
||||
archive, err := platformpvf.Open(*pvfPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "open:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
townList, err := archive.ReadText("town/town.lst")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "read town.lst:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
towns := []string{}
|
||||
for _, m := range regexp.MustCompile("`([^`]+\\.twn)`").FindAllStringSubmatch(townList, -1) {
|
||||
towns = append(towns, "town/"+m[1])
|
||||
}
|
||||
|
||||
index := map[string]int{}
|
||||
all := archive.Files()
|
||||
for _, f := range all {
|
||||
index[strings.ToLower(f.ArchivePath)] = f.Index
|
||||
}
|
||||
fmt.Printf("城镇数=%d 归档文件=%d 查找 NPC=%d\n\n", len(towns), len(all), *npcID)
|
||||
|
||||
found := 0
|
||||
scannedMaps := map[string]bool{}
|
||||
for _, twn := range towns {
|
||||
twnText, err := archive.ReadText(twn)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var maps []string
|
||||
for _, m := range areaRe.FindAllStringSubmatch(twnText, -1) {
|
||||
maps = append(maps, m[1])
|
||||
}
|
||||
hits := []string{}
|
||||
for _, mp := range maps {
|
||||
// .twn 里写的是 "Cataclysm/Town/...",归档里在 "map/" 前缀下。
|
||||
candidates := []string{
|
||||
strings.ToLower(mp),
|
||||
strings.ToLower("map/" + mp),
|
||||
}
|
||||
resolved := false
|
||||
for _, cand := range candidates {
|
||||
idx, ok := index[cand]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
resolved = true
|
||||
scannedMaps[cand] = true
|
||||
text, err := archive.FileText(idx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if npcIDInMap(text, *npcID) {
|
||||
hits = append(hits, cand)
|
||||
}
|
||||
break
|
||||
}
|
||||
if !resolved && *verbose {
|
||||
fmt.Printf(" [缺失] %s\n", mp)
|
||||
}
|
||||
}
|
||||
if *verbose {
|
||||
fmt.Printf("%-42s 地图 %d 命中 %d\n", twn, len(maps), len(hits))
|
||||
}
|
||||
if len(hits) > 0 {
|
||||
found++
|
||||
fmt.Printf("%s\n", twn)
|
||||
for _, h := range hits {
|
||||
fmt.Printf(" %s\n", h)
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == 0 {
|
||||
fmt.Printf("没有城镇地图的 [NPC] 段包含 NPC %d(已扫描 %d 张地图)\n", *npcID, len(scannedMaps))
|
||||
} else {
|
||||
fmt.Printf("\n共 %d 个城镇包含 NPC %d\n", found, *npcID)
|
||||
}
|
||||
sort.Strings(towns)
|
||||
}
|
||||
|
||||
func npcIDInMap(mapText string, id int64) bool {
|
||||
inNPC := false
|
||||
for _, line := range strings.Split(strings.ReplaceAll(mapText, "\r\n", "\n"), "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "[") {
|
||||
inNPC = strings.EqualFold(trimmed, "[npc]")
|
||||
continue
|
||||
}
|
||||
if !inNPC {
|
||||
continue
|
||||
}
|
||||
// 记录之间用 `[方向]` 或空白分隔。先把 `[方向]` 当分隔符拆开,
|
||||
// 再按块取每块第一个整数。
|
||||
for _, chunk := range quoteRe.Split(line, -1) {
|
||||
m := intRe.FindString(chunk)
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
got, err := strconv.ParseInt(m, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if got == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user