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,128 @@
// pvfcollectboxbase 把 PVF **共享字符串池**里收集箱的 [base expiration date] 改成未来日期。
//
// 原因见 internal/modules/dnf/collectbox/owner.go 的 SnapshotBonus:
//
// expiration := state.Expiration[boxIndex] // Insert(放卡片) 不写它;只有 Extend(延期券) 写
// if expiration.IsZero() {
// expiration, _ = parseExpiration(definition.BaseExpirationDate) // ← 回退到 base
// }
// if !expiration.IsZero() && !now.Before(expiration) { return Bonus{} } // 过期 → 属性不生效
//
// 而 localization/collectbox/collectboxinfo.etc 里各箱 base 日期均已是过去:
//
// Index1 SAO 2014-12-30 Index2 生肖之灵 2015-09-01
// Index3 三国志 2017-01-05 Index4 猎龙者 2017-12-31
// Index5 兽人守护神 2018-12-31
//
// 用 RepackStringValues 做**等宽**替换:所有日期都是 19 字符,替换后长度不变,
// 池内偏移与所有引用保持有效。
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
platformpvf "longheng.io/server/internal/platform/pvf"
)
var replacements = []platformpvf.StringValueReplacement{
{Old: "2014-12-30 06:00:00", New: "2099-12-30 06:00:00"},
{Old: "2015-09-01 06:00:00", New: "2099-09-01 06:00:00"},
{Old: "2017-01-05 06:00:00", New: "2099-01-05 06:00:00"},
{Old: "2017-12-31 06:00:00", New: "2099-12-31 06:00:00"},
{Old: "2018-12-31 06:00:00", New: "2099-12-31 06:00:00"},
}
func main() {
input := flag.String("pvf", "", "input PVF (in place)")
flag.Parse()
if *input == "" {
panic("-pvf is required")
}
archive, err := platformpvf.Open(*input)
if err != nil {
panic(err)
}
// 先确认这些日期确实在池里
offsets := archive.FindStringMagicOffsets(func() []string {
out := make([]string, 0, len(replacements))
for _, r := range replacements {
out = append(out, r.Old)
}
return out
}())
fmt.Println("池中命中情况:")
ok := make([]platformpvf.StringValueReplacement, 0, len(replacements))
for _, r := range replacements {
if off, found := offsets[r.Old]; found {
fmt.Printf(" ✓ %s (池@%d) → %s\n", r.Old, off, r.New)
ok = append(ok, r)
} else {
fmt.Printf(" ✗ %s 不在池中\n", r.Old)
}
}
if len(ok) == 0 {
panic("没有任何可替换的日期")
}
repacked, err := archive.RepackStringValues(ok)
if err != nil {
panic(err)
}
fmt.Printf("重打包完成:%d → %d 字节\n", len(archive.Bytes()), len(repacked))
// 校验:新 PVF 里日期已变
verify, err := platformpvf.OpenBytes(repacked)
if err != nil {
fmt.Println("⚠ 无法重新打开校验:", err)
} else {
text, err := verify.ReadText("localization/collectbox/collectboxinfo.etc")
if err == nil {
for _, r := range ok {
if contains(text, r.New) {
fmt.Printf(" ✓ 已确认含 %s\n", r.New)
}
if contains(text, r.Old) {
fmt.Printf(" ✗ 仍含旧值 %s\n", r.Old)
}
}
} else {
fmt.Println(" ⚠ 校验读取失败:", err)
}
}
// 原子写回:先写临时文件再替换
dir := filepath.Dir(*input)
tmp := filepath.Join(dir, filepath.Base(*input)+".tmp_collectbox")
if err := os.WriteFile(tmp, repacked, 0o644); err != nil {
panic(err)
}
backup := *input + ".bak_before_basefix"
if _, statErr := os.Stat(backup); statErr != nil {
if err := os.Rename(*input, backup); err != nil {
panic(err)
}
fmt.Println("已备份 →", backup)
} else {
if err := os.Remove(*input); err != nil {
panic(err)
}
}
if err := os.Rename(tmp, *input); err != nil {
panic(err)
}
fmt.Println("已写回", *input)
}
func contains(haystack, needle string) bool {
if len(needle) == 0 || len(haystack) < len(needle) {
return false
}
for i := 0; i+len(needle) <= len(haystack); i++ {
if haystack[i:i+len(needle)] == needle {
return true
}
}
return false
}