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,169 @@
// pvfrawdisableattendance 从 Script.pvf 读出「累计在线得好礼」活动定义文件的
// **原始字节**,把奖励与阶段数据等长清空,导出到 pvfrepack 所需的 raw 目录结构,
// 之后由 pvfrepack 重新封包。
//
// 为什么必须走 raw:
//
// pvfrepack 明确说明"decoded 可读文本不能直接重新封包"——直接用 ReadText 得到的
// 文本重新写回会破坏 type-1 成员的令牌流(实测:重打包后该文件只剩 1 行)。
// 所以这里只改 raw 字节,且**保持文件长度完全不变**。
//
// 目标文件:event/chn_event/chn_attendanceevent.evt
//
// go run ./cmd/scratch/pvfrawdisableattendance -pvf <in.pvf> -dir <raw目录>
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
platformpvf "longheng.io/server/internal/platform/pvf"
)
const targetPath = "event/chn_event/chn_attendanceevent.evt"
func main() {
pvfPath := flag.String("pvf", "", "源 Script.pvf")
dir := flag.String("dir", "", "raw 修改目录(按 PVF 相对路径存放)")
flag.Parse()
if *pvfPath == "" || *dir == "" {
fmt.Fprintln(os.Stderr, "usage: pvfrawdisableattendance -pvf <in.pvf> -dir <rawdir>")
os.Exit(2)
}
archive, err := platformpvf.Open(*pvfPath)
if err != nil {
fmt.Fprintln(os.Stderr, "open:", err)
os.Exit(1)
}
raw, err := archive.ReadRaw(targetPath)
if err != nil {
fmt.Fprintln(os.Stderr, "read raw:", err)
os.Exit(1)
}
fmt.Printf("原始字节 %d\n", len(raw))
patched := patchRaw(raw)
if len(patched) != len(raw) {
fmt.Fprintf(os.Stderr, "!! 长度变化 %d -> %d,中止\n", len(raw), len(patched))
os.Exit(1)
}
fmt.Printf("改后字节 %d(等长)\n", len(patched))
out := filepath.Join(*dir, filepath.FromSlash(targetPath))
if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil {
fmt.Fprintln(os.Stderr, "mkdir:", err)
os.Exit(1)
}
if err := os.WriteFile(out, patched, 0o644); err != nil {
fmt.Fprintln(os.Stderr, "write:", err)
os.Exit(1)
}
fmt.Printf("已写出 raw: %s\n", out)
}
// patchRaw 在原始字节上做等长替换。
//
// type-1 成员里,段标签与数值都是"反引号包裹的 ASCII 字面量"(例如 `1800`),
// 因此只需在 ASCII 范围内把数字位换成 9、把奖励物品号整段换成空格,长度不变。
func patchRaw(raw []byte) []byte {
out := make([]byte, len(raw))
copy(out, raw)
// 1) [reward item] / [reward item for sum] 段内的物品号清空。
out = blankRawSection(out, "reward item")
out = blankRawSection(out, "reward item for sum")
// 2) [process time seconds] / [process seconds for max count] 的数值改为极大,
// 使阶段永远不可达(等长:数字位数不变)。
out = maxRawSectionNumbers(out, "process time seconds")
out = maxRawSectionNumbers(out, "process seconds for max count")
return out
}
// findRawTag 在原始字节里找到以 `[tag]` 形式出现的偏移。
func findRawTag(data []byte, tag string) int {
needle := []byte("[`" + tag + "`]")
if i := strings.Index(string(data), string(needle)); i >= 0 {
return i
}
// 兼容无内层反引号的写法
needle2 := []byte("[" + tag + "]")
return strings.Index(string(data), string(needle2))
}
// blankRawSection 把 `[tag] ... [/tag]` 之间的可打印字符(非结构性 '`')换成空格。
func blankRawSection(data []byte, tag string) []byte {
start := findRawTag(data, tag)
if start < 0 {
fmt.Printf(" 提示:未找到段 [%s]\n", tag)
return data
}
openEnd := start
for openEnd < len(data) && data[openEnd] != ']' {
openEnd++
}
openEnd++ // 越过 ']'
closeTag := "[/`" + tag + "`]"
end := strings.Index(string(data[openEnd:]), closeTag)
if end < 0 {
closeTag = "[/" + tag + "]"
end = strings.Index(string(data[openEnd:]), closeTag)
}
if end < 0 {
fmt.Printf(" 提示:[%s] 没有闭合标签\n", tag)
return data
}
bodyEnd := openEnd + end
for i := openEnd; i < bodyEnd; i++ {
c := data[i]
// 只改数字,避免碰结构字符(反引号、方括号、换行)
if c >= '0' && c <= '9' {
data[i] = ' '
}
}
return data
}
// maxRawSectionNumbers 把段内数字改为 9(等长),使门槛极大。
func maxRawSectionNumbers(data []byte, tag string) []byte {
start := findRawTag(data, tag)
if start < 0 {
fmt.Printf(" 提示:未找到段 [%s]\n", tag)
return data
}
openEnd := start
for openEnd < len(data) && data[openEnd] != ']' {
openEnd++
}
openEnd++
closeTag := "[/`" + tag + "`]"
end := strings.Index(string(data[openEnd:]), closeTag)
if end < 0 {
closeTag = "[/" + tag + "]"
end = strings.Index(string(data[openEnd:]), closeTag)
}
if end < 0 {
return data
}
bodyEnd := openEnd + end
re := regexp.MustCompile(`[0-9]+`)
seg := data[openEnd:bodyEnd]
for _, loc := range re.FindAllIndex(seg, -1) {
for i := loc[0]; i < loc[1]; i++ {
seg[i] = '9'
}
}
return data
}