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,147 @@
// 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), "字节")
}