服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
148 lines
3.5 KiB
Go
148 lines
3.5 KiB
Go
// pvfdigestrestructure 重构 etc/storydigest/digest.etc 的段边界与 [ending],
|
||
// 使 13 个城镇区域的过场能在各自开区等级所在的段播出。
|
||
//
|
||
// 只改 digest.etc 的整数 token,不动 scene.etc、不新增共享池字符串。
|
||
package main
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"flag"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||
)
|
||
|
||
// 实测的池引用(digest.etc)
|
||
const (
|
||
refLevel = 0x003566E2 // "[level]"
|
||
refEnding = 0x025138D6 // "[ending]"
|
||
)
|
||
|
||
type seg struct {
|
||
idx int
|
||
lo, hi int
|
||
ending int
|
||
}
|
||
|
||
var layout = []seg{
|
||
{1, 17, 23, 901},
|
||
{2, 24, 29, 902},
|
||
{3, 30, 34, 903},
|
||
{4, 35, 35, 904},
|
||
{5, 36, 39, 905},
|
||
{6, 40, 49, 906},
|
||
{7, 50, 62, 912},
|
||
{8, 63, 70, 908},
|
||
{9, 71, 80, 909},
|
||
{10, 81, 84, 900},
|
||
{11, 85, 86, 915},
|
||
{12, 87, 88, 916},
|
||
{13, 89, 89, 917},
|
||
{14, 90, 90, 913},
|
||
{15, 91, 91, 911},
|
||
{16, 92, 92, 910},
|
||
{17, 93, 999, 907},
|
||
}
|
||
|
||
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||
func put32(b []byte, v uint32) { binary.LittleEndian.PutUint32(b, v) }
|
||
|
||
func main() {
|
||
input := flag.String("pvf", "", "input PVF")
|
||
dir := flag.String("dir", "", "raw patch directory output")
|
||
dry := flag.Bool("dry-run", false, "只打印,不写文件")
|
||
flag.Parse()
|
||
archive, err := platformpvf.Open(*input)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
raw, err := archive.ReadRaw("etc/storydigest/digest.etc")
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
toks := len(raw) / 5
|
||
isSec := func(i int, ref uint32) bool {
|
||
return i < toks && raw[i*5] == 3 && u32(raw[i*5+1:i*5+5]) == ref
|
||
}
|
||
isInt := func(i int) bool { return i < toks && raw[i*5] == 0 }
|
||
|
||
fmt.Println("digest.etc 令牌:", toks)
|
||
|
||
type slot struct{ loTok, hiTok, endTok int }
|
||
var slots []slot
|
||
for i := 0; i < toks; i++ {
|
||
if !isSec(i, refLevel) {
|
||
continue
|
||
}
|
||
if !isInt(i+1) || !isInt(i+2) {
|
||
panic(fmt.Sprintf("[level]@%d 后不是两个整数", i))
|
||
}
|
||
endTok := -1
|
||
for j := i + 3; j < toks; j++ {
|
||
if isSec(j, refEnding) {
|
||
if !isInt(j + 1) {
|
||
panic(fmt.Sprintf("[ending]@%d 后不是整数", j))
|
||
}
|
||
endTok = j + 1
|
||
break
|
||
}
|
||
if isSec(j, refLevel) {
|
||
break
|
||
}
|
||
}
|
||
if endTok < 0 {
|
||
panic(fmt.Sprintf("第 %d 个 [level] 后找不到 [ending]", len(slots)+1))
|
||
}
|
||
slots = append(slots, slot{i + 1, i + 2, endTok})
|
||
}
|
||
fmt.Println("解析出段数:", len(slots))
|
||
if len(slots) != len(layout) {
|
||
panic(fmt.Sprintf("段数 %d 与布局 %d 不一致", len(slots), len(layout)))
|
||
}
|
||
|
||
fmt.Println("--- 现状 ---")
|
||
for i, s := range slots {
|
||
fmt.Printf(" 段%-3d level %5d-%-5d ending %d\n", i+1,
|
||
u32(raw[s.loTok*5+1:]), u32(raw[s.hiTok*5+1:]), u32(raw[s.endTok*5+1:]))
|
||
}
|
||
|
||
for i, s := range slots {
|
||
w := layout[i]
|
||
put32(raw[s.loTok*5+1:], uint32(w.lo))
|
||
put32(raw[s.hiTok*5+1:], uint32(w.hi))
|
||
put32(raw[s.endTok*5+1:], uint32(w.ending))
|
||
}
|
||
|
||
fmt.Println("--- 应用后 ---")
|
||
ok := true
|
||
for i, s := range slots {
|
||
lo := int(u32(raw[s.loTok*5+1:]))
|
||
hi := int(u32(raw[s.hiTok*5+1:]))
|
||
e := int(u32(raw[s.endTok*5+1:]))
|
||
mark := "OK"
|
||
if lo != layout[i].lo || hi != layout[i].hi || e != layout[i].ending {
|
||
mark = "MISMATCH"
|
||
ok = false
|
||
}
|
||
fmt.Printf(" 段%-3d level %5d-%-5d ending %-4d %s\n", i+1, lo, hi, e, mark)
|
||
}
|
||
if !ok {
|
||
panic("应用结果与期望不符")
|
||
}
|
||
|
||
if *dry {
|
||
fmt.Println("dry-run,未写文件")
|
||
return
|
||
}
|
||
full := filepath.Join(*dir, "etc", "storydigest", "digest.etc")
|
||
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
||
panic(err)
|
||
}
|
||
if err := os.WriteFile(full, raw, 0o644); err != nil {
|
||
panic(err)
|
||
}
|
||
fmt.Println("已写出:", full, len(raw), "字节")
|
||
}
|