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,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
|
||||
}
|
||||
Reference in new issue
Block a user