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,228 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
const probePVFPath = `DNF_REAL_PVF`
|
||||
|
||||
type probeItem struct {
|
||||
path string
|
||||
doc *dnfpvf.Document
|
||||
text string
|
||||
}
|
||||
|
||||
func loadProbeEqu(t *testing.T) (*platformpvf.Archive, []probeItem) {
|
||||
t.Helper()
|
||||
archive, err := platformpvf.Open(probePVFPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items := []probeItem{}
|
||||
archive.RangePaths(func(p string) bool {
|
||||
low := strings.ToLower(p)
|
||||
if !strings.HasSuffix(low, ".equ") {
|
||||
return true
|
||||
}
|
||||
text, err := archive.ReadText(p)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if !strings.Contains(text, "[skill levelup]") && !strings.Contains(text, "[all skill item]") {
|
||||
return true
|
||||
}
|
||||
doc, perr := dnfpvf.Parse(p, text)
|
||||
if perr != nil {
|
||||
return true
|
||||
}
|
||||
items = append(items, probeItem{path: p, doc: doc, text: text})
|
||||
return true
|
||||
})
|
||||
return archive, items
|
||||
}
|
||||
|
||||
func TestZZProbeRealBuffCoverage(t *testing.T) {
|
||||
archive, items := loadProbeEqu(t)
|
||||
ctx := context.Background()
|
||||
table, err := buildSkillCatalogFromSource(ctx, archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
character := dnfrepo.CharacterRecord{Job: "4", Stats: map[string]int64{"grow_type": 1}}
|
||||
candidates, err := skillSwitchSupportSkillsByPVFSource(archive, character)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jobTag := "priest"
|
||||
stage := byte(1)
|
||||
fmt.Printf("PROBE items scanned=%d priest(4) grow=1 candidates=%v\n", len(items), candidates)
|
||||
for _, cand := range candidates {
|
||||
def, ok := table.Find(4, cand)
|
||||
req, active, name := 0, false, ""
|
||||
if ok {
|
||||
req, active, name = def.RequiredLevel, def.Active, def.Name
|
||||
}
|
||||
exact, ranged, both, totalNonZero := 0, 0, 0, 0
|
||||
best := ""
|
||||
for _, it := range items {
|
||||
bonus := skillSwitchDocumentBuffLevelBonus(it.doc, jobTag, cand, req, active, stage)
|
||||
_ = best
|
||||
// split contributions: exact-only by re-running with range disabled is
|
||||
// not exposed; approximate by checking block presence.
|
||||
if bonus == 0 {
|
||||
continue
|
||||
}
|
||||
totalNonZero++
|
||||
hasLevelup := strings.Contains(it.text, "[skill levelup]")
|
||||
hasAllSkill := strings.Contains(it.text, "[all skill item]")
|
||||
switch {
|
||||
case hasLevelup && hasAllSkill:
|
||||
both++
|
||||
case hasLevelup:
|
||||
exact++
|
||||
case hasAllSkill:
|
||||
ranged++
|
||||
}
|
||||
}
|
||||
fmt.Printf("PROBE cand=%d name=%q requiredLevel=%d active=%v -> itemsWithBonus=%d (exactOnly=%d rangeOnly=%d mixed=%d)\n",
|
||||
cand, name, req, active, totalNonZero, exact, ranged, both)
|
||||
}
|
||||
|
||||
// Sample the levelup rows for a couple of concrete items to inspect tag layout.
|
||||
samples := 0
|
||||
for _, it := range items {
|
||||
if samples >= 3 {
|
||||
break
|
||||
}
|
||||
for _, sec := range it.doc.Sections {
|
||||
if sectionName(sec.Name) != "skill levelup" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(it.text, "[swordman]") {
|
||||
continue
|
||||
}
|
||||
raw := ""
|
||||
for _, tok := range it.doc.Tokens[sec.Start:sec.End] {
|
||||
raw += tok.Raw + " "
|
||||
}
|
||||
fmt.Printf("PROBE sample levelup path=%s rows=%s\n", it.path, strings.TrimSpace(raw))
|
||||
samples++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-tag same-skill rows: evidence for job-tag exclusivity.
|
||||
dupSamples := 0
|
||||
for _, it := range items {
|
||||
if dupSamples >= 4 {
|
||||
break
|
||||
}
|
||||
for _, sec := range it.doc.Sections {
|
||||
if sectionName(sec.Name) != "skill levelup" {
|
||||
continue
|
||||
}
|
||||
bySkill := map[int64][]string{}
|
||||
toks := it.doc.Tokens[sec.Start:sec.End]
|
||||
for i := 0; i+2 < len(toks); i += 3 {
|
||||
if toks[i+1].Kind == dnfpvf.TokenInt {
|
||||
bySkill[toks[i+1].Int] = append(bySkill[toks[i+1].Int], toks[i].Raw)
|
||||
}
|
||||
}
|
||||
for skill, tags := range bySkill {
|
||||
if len(tags) > 1 {
|
||||
fmt.Printf("PROBE multiset path=%s skill=%d tags=%v\n", it.path, skill, tags)
|
||||
dupSamples++
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZZProbeCommonTagAndSkillLevelTag(t *testing.T) {
|
||||
archive, items := loadProbeEqu(t)
|
||||
ctx := context.Background()
|
||||
table, err := buildSkillCatalogFromSource(ctx, archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
commonSkills := map[int64]int{}
|
||||
for _, it := range items {
|
||||
for _, sec := range it.doc.Sections {
|
||||
if sectionName(sec.Name) != "skill levelup" {
|
||||
continue
|
||||
}
|
||||
toks := it.doc.Tokens[sec.Start:sec.End]
|
||||
for i := 0; i+2 < len(toks); i += 3 {
|
||||
if strings.ToLower(strings.Trim(toks[i].Raw, "`")) != "common" {
|
||||
continue
|
||||
}
|
||||
if toks[i+1].Kind == dnfpvf.TokenInt {
|
||||
commonSkills[toks[i+1].Int]++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keys := make([]int, 0, len(commonSkills))
|
||||
for k := range commonSkills {
|
||||
keys = append(keys, int(k))
|
||||
}
|
||||
sort.Ints(keys)
|
||||
fmt.Printf("PROBE [common] rows distinctSkills=%d\n", len(keys))
|
||||
shown := 0
|
||||
for _, k := range keys {
|
||||
name := ""
|
||||
for job := byte(0); job < 16; job++ {
|
||||
if def, ok := table.Find(job, uint16(k)); ok {
|
||||
name = def.Name + " (job " + fmt.Sprint(job) + ")"
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Printf("PROBE common skill=%d rows=%d name=%q\n", k, commonSkills[int64(k)], name)
|
||||
shown++
|
||||
if shown >= 20 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Which file types carry [SKILL_LEVEL]? Only the small data/config
|
||||
// extensions are decompressed here to keep the probe cheap.
|
||||
byExt := map[string]int{}
|
||||
archive.RangePaths(func(p string) bool {
|
||||
low := strings.ToLower(p)
|
||||
want := false
|
||||
for _, ext := range []string{".dat", ".etc", ".lst", ".skl", ".txt", ".ini"} {
|
||||
if strings.HasSuffix(low, ext) {
|
||||
want = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !want {
|
||||
return true
|
||||
}
|
||||
text, err := archive.ReadText(p)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(text, "[SKILL_LEVEL]") {
|
||||
idx := strings.LastIndex(low, ".")
|
||||
ext := low[idx:]
|
||||
byExt[ext]++
|
||||
if byExt[ext] <= 3 {
|
||||
fmt.Printf("PROBE [SKILL_LEVEL] hit in %s\n", p)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
fmt.Printf("PROBE [SKILL_LEVEL] by extension: %v\n", byExt)
|
||||
}
|
||||
Reference in new issue
Block a user