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,188 @@
|
||||
// pvfdisableattendance 把客户端 Script.pvf 里「累计在线得好礼」(event 2347) 的
|
||||
// 活动定义清空,使客户端不再显示该活动。
|
||||
//
|
||||
// 目标文件:event/chn_event/chn_attendanceevent.evt
|
||||
//
|
||||
// [process time seconds] 1800 1800 3600 3600 ← 四阶段时长
|
||||
// [reward item] 490006574..490006577 ← 阶段奖励
|
||||
// [process seconds for max count] 5 10 15 25 ← 25 次满阶段
|
||||
// [reward item for sum] 490006578..490006581 ← 满阶段奖励
|
||||
//
|
||||
// 做法:把 [reward item]、[reward item for sum] 两个段的**内容行**清空(保留段标签),
|
||||
// 并把 [process time seconds] 的数值改为一个极大值,使阶段永远不可达。
|
||||
//
|
||||
// 刻意保持**总字节数不变**(用空格填充),因为归档重打包对文件长度变化更敏感;
|
||||
// 等长替换把风险降到最低。
|
||||
//
|
||||
// 用法:
|
||||
//
|
||||
// go run ./cmd/scratch/pvfdisableattendance -pvf <in.pvf> -out <out.pvf>
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"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")
|
||||
outPath := flag.String("out", "", "输出 Script.pvf(不覆盖源文件)")
|
||||
flag.Parse()
|
||||
|
||||
if *pvfPath == "" || *outPath == "" {
|
||||
fmt.Fprintln(os.Stderr, "usage: pvfdisableattendance -pvf <in> -out <out>")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
archive, err := platformpvf.Open(*pvfPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "open:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
text, err := archive.ReadText(targetPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "read target:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("原文件 %d 字节\n", len(text))
|
||||
fmt.Printf("---- 原文 ----\n%s\n--------------\n", text)
|
||||
|
||||
patched := disableActivity(text)
|
||||
if len(patched) != len(text) {
|
||||
fmt.Fprintf(os.Stderr, "!! 长度变化 %d -> %d,中止\n", len(text), len(patched))
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("---- 改后(等长 %d 字节)----\n%s\n--------------\n", len(patched), patched)
|
||||
|
||||
raw, err := archive.RepackRaw([]platformpvf.RawReplacement{{Path: targetPath, Data: []byte(patched)}})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "repack:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.WriteFile(*outPath, raw, 0o644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "write:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("已写出: %s (%d 字节)\n", *outPath, len(raw))
|
||||
}
|
||||
|
||||
// disableActivity 在等长前提下清空奖励内容。
|
||||
func disableActivity(text string) string {
|
||||
out := text
|
||||
|
||||
// 逐段处理:段标签之间的内容行替换为等长空白。
|
||||
out = blankSectionBody(out, "reward item")
|
||||
out = blankSectionBody(out, "reward item for sum")
|
||||
|
||||
// [process time seconds] 的数值改为极大值(等长:用同样位数的数字)。
|
||||
out = rewriteSectionNumbers(out, "process time seconds", "999999999")
|
||||
out = rewriteSectionNumbers(out, "process seconds for max count", "999999999")
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// blankSectionBody 把 [name] ... [/name] 之间的非空内容行清成空格(长度不变)。
|
||||
// 段落名匹配用宽松规则:"[reward item]" 与 "[reward item for sum]" 都要能命中,
|
||||
// 所以按精确标签匹配。
|
||||
func blankSectionBody(text, name string) string {
|
||||
open := "[" + name + "]"
|
||||
closeTag := "[/" + name + "]"
|
||||
|
||||
start := indexOfLine(text, open)
|
||||
if start < 0 {
|
||||
fmt.Printf(" 提示:未找到段 %s\n", open)
|
||||
return text
|
||||
}
|
||||
bodyStart := start + len(open)
|
||||
end := strings.Index(text[bodyStart:], closeTag)
|
||||
if end < 0 {
|
||||
fmt.Printf(" 提示:段 %s 没有闭合标签\n", open)
|
||||
return text
|
||||
}
|
||||
body := text[bodyStart : bodyStart+end]
|
||||
|
||||
// 逐行:保留换行,把可见字符换成空格。
|
||||
var b strings.Builder
|
||||
b.Grow(len(body))
|
||||
for _, line := range strings.SplitAfter(body, "\n") {
|
||||
nl := ""
|
||||
content := line
|
||||
if strings.HasSuffix(line, "\n") {
|
||||
nl = "\n"
|
||||
content = strings.TrimSuffix(line, "\n")
|
||||
}
|
||||
b.WriteString(strings.Repeat(" ", len(content)))
|
||||
b.WriteString(nl)
|
||||
}
|
||||
return text[:bodyStart] + b.String() + text[bodyStart+end:]
|
||||
}
|
||||
|
||||
// rewriteSectionNumbers 把段内所有数字替换为 given(逐行等长填充)。
|
||||
func rewriteSectionNumbers(text, name, value string) string {
|
||||
open := "[" + name + "]"
|
||||
closeTag := "[/" + name + "]"
|
||||
|
||||
start := indexOfLine(text, open)
|
||||
if start < 0 {
|
||||
fmt.Printf(" 提示:未找到段 %s\n", open)
|
||||
return text
|
||||
}
|
||||
bodyStart := start + len(open)
|
||||
end := strings.Index(text[bodyStart:], closeTag)
|
||||
if end < 0 {
|
||||
return text
|
||||
}
|
||||
body := text[bodyStart : bodyStart+end]
|
||||
|
||||
numRe := regexp.MustCompile(`\d+`)
|
||||
var b strings.Builder
|
||||
b.Grow(len(body))
|
||||
for _, line := range strings.SplitAfter(body, "\n") {
|
||||
nl := ""
|
||||
content := line
|
||||
if strings.HasSuffix(line, "\n") {
|
||||
nl = "\n"
|
||||
content = strings.TrimSuffix(line, "\n")
|
||||
}
|
||||
replaced := numRe.ReplaceAllStringFunc(content, func(m string) string {
|
||||
// 等长:用 value 的前 len(m) 个字符;若 value 更短则补 '9'
|
||||
if len(m) <= len(value) {
|
||||
return value[:len(m)]
|
||||
}
|
||||
return strings.Repeat("9", len(m))
|
||||
})
|
||||
// 保持行长度
|
||||
if len(replaced) < len(content) {
|
||||
replaced += strings.Repeat(" ", len(content)-len(replaced))
|
||||
}
|
||||
b.WriteString(replaced)
|
||||
b.WriteString(nl)
|
||||
}
|
||||
return text[:bodyStart] + b.String() + text[bodyStart+end:]
|
||||
}
|
||||
|
||||
// indexOfLine 找到以给定标签开头的那一行的偏移。
|
||||
func indexOfLine(text, tag string) int {
|
||||
idx := 0
|
||||
for {
|
||||
at := strings.Index(text[idx:], tag)
|
||||
if at < 0 {
|
||||
return -1
|
||||
}
|
||||
at += idx
|
||||
// 必须位于行首(允许前导空白)
|
||||
lineStart := strings.LastIndexByte(text[:at], '\n') + 1
|
||||
if strings.TrimSpace(text[lineStart:at]) == "" {
|
||||
return at
|
||||
}
|
||||
idx = at + len(tag)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user