服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
// 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
|
||
}
|