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,214 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"pvfine/internal/pvf"
|
||||
)
|
||||
|
||||
type shopLocation struct {
|
||||
path []string
|
||||
occurrence int
|
||||
implicit bool
|
||||
}
|
||||
|
||||
type shopNode struct {
|
||||
element pvf.ScriptElement
|
||||
values []pvf.ScriptElement
|
||||
children []*shopNode
|
||||
location shopLocation
|
||||
}
|
||||
|
||||
func parseShop(text string) *ShopDocument {
|
||||
doc := &ShopDocument{Categories: []ShopCategory{}, Tabs: []ShopTab{}, Issues: []PreviewIssue{}}
|
||||
root := &shopNode{}
|
||||
stack := []*shopNode{root}
|
||||
nodes := map[int]*shopNode{}
|
||||
ordered := []*shopNode{}
|
||||
counts := map[string]int{}
|
||||
for _, e := range pvf.ParseScriptView(text).Elements {
|
||||
if e.Kind == pvf.ScriptElementToken {
|
||||
if n := nodes[e.SectionID]; n != nil {
|
||||
n.values = append(n.values, e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if e.Kind != pvf.ScriptElementSection {
|
||||
continue
|
||||
}
|
||||
depth := len(e.SectionPath)
|
||||
if depth > len(stack) {
|
||||
continue
|
||||
}
|
||||
stack = stack[:depth]
|
||||
key := strings.Join(e.SectionPath, "/")
|
||||
n := &shopNode{element: e, location: shopLocation{path: e.SectionPath, occurrence: counts[key]}}
|
||||
counts[key]++
|
||||
parent := stack[len(stack)-1]
|
||||
parent.children = append(parent.children, n)
|
||||
nodes[e.SectionID] = n
|
||||
ordered = append(ordered, n)
|
||||
stack = append(stack, n)
|
||||
}
|
||||
categoryTypes := 0
|
||||
for _, n := range ordered {
|
||||
if strings.Join(n.element.SectionPath, "/") == "sell info/use category" {
|
||||
categoryTypes++
|
||||
doc.CategoryType = shopNodeFirst(n)
|
||||
}
|
||||
}
|
||||
if categoryTypes > 1 {
|
||||
shopIssue(doc, "use category", "商店包含重复的大分类声明")
|
||||
}
|
||||
consumed := map[*shopNode]bool{}
|
||||
addTab := func(n *shopNode, implicit bool) {
|
||||
name := shopNodeFirst(n)
|
||||
if implicit {
|
||||
name = "商品"
|
||||
}
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("分页 %d", len(doc.Tabs)+1)
|
||||
}
|
||||
tab := ShopTab{Name: name, SourceStart: n.element.Start, Groups: []ShopGroup{}, location: n.location}
|
||||
tab.location.implicit = implicit
|
||||
tab.Implicit = implicit
|
||||
for _, child := range n.children {
|
||||
switch child.element.Section {
|
||||
case "item list", "sell item list":
|
||||
group := ShopGroup{SourceStart: child.element.Start, Items: []ShopEntry{}}
|
||||
parseShopList(child, &group, doc)
|
||||
consumed[child] = true
|
||||
tab.Groups = append(tab.Groups, group)
|
||||
case "category entry":
|
||||
group := ShopGroup{SourceStart: child.element.Start, Items: []ShopEntry{}}
|
||||
for _, field := range child.children {
|
||||
switch field.element.Section {
|
||||
case "id":
|
||||
group.CategoryID = shopNodeFirst(field)
|
||||
case "item list", "sell item list":
|
||||
parseShopList(field, &group, doc)
|
||||
consumed[field] = true
|
||||
}
|
||||
}
|
||||
if _, err := strconv.ParseInt(group.CategoryID, 10, 32); err != nil {
|
||||
shopIssue(doc, "category entry", "分类 ID 无效:"+group.CategoryID)
|
||||
}
|
||||
tab.Groups = append(tab.Groups, group)
|
||||
}
|
||||
}
|
||||
doc.Tabs = append(doc.Tabs, tab)
|
||||
}
|
||||
for _, n := range root.children {
|
||||
switch n.element.Section {
|
||||
case "tab":
|
||||
addTab(n, false)
|
||||
case "sell info":
|
||||
direct := false
|
||||
for _, child := range n.children {
|
||||
switch child.element.Section {
|
||||
case "tab":
|
||||
addTab(child, false)
|
||||
case "item list", "sell item list", "category entry":
|
||||
direct = true
|
||||
}
|
||||
}
|
||||
if direct {
|
||||
addTab(n, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, n := range ordered {
|
||||
if (n.element.Section == "item list" || n.element.Section == "sell item list") && !consumed[n] {
|
||||
shopIssue(doc, n.element.Section, "无法识别商品列表所在的商店结构")
|
||||
}
|
||||
}
|
||||
categories := map[string]bool{}
|
||||
for _, tab := range doc.Tabs {
|
||||
for _, group := range tab.Groups {
|
||||
if group.CategoryID != "" && !categories[group.CategoryID] {
|
||||
categories[group.CategoryID] = true
|
||||
doc.Categories = append(doc.Categories, ShopCategory{ID: group.CategoryID})
|
||||
}
|
||||
if doc.CategoryType != "" && group.CategoryID == "" {
|
||||
shopIssue(doc, "category entry", "分类商品缺少分类 ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
if doc.CategoryType == "" && len(doc.Categories) > 0 {
|
||||
shopIssue(doc, "use category", "存在分类商品,但未声明大分类类型")
|
||||
}
|
||||
if len(doc.Tabs) == 0 {
|
||||
shopIssue(doc, "sell info", "未找到可展示的商店分页,请检查文本结构")
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
func shopNodeFirst(n *shopNode) string {
|
||||
if len(n.values) > 0 {
|
||||
return n.values[0].Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseShopList(n *shopNode, group *ShopGroup, doc *ShopDocument) {
|
||||
appendID := func(v pvf.ScriptElement) *ShopEntry {
|
||||
id, err := strconv.ParseInt(v.Value, 10, 32)
|
||||
if err != nil || id < 0 || v.TokenType != 0 {
|
||||
shopIssue(doc, n.element.Section, "商品 ID 无效:"+v.Value)
|
||||
return nil
|
||||
}
|
||||
group.Items = append(group.Items, ShopEntry{Item: ShopItem{ID: v.Value}, SourceStart: v.Start, SourceEnd: v.End})
|
||||
return &group.Items[len(group.Items)-1]
|
||||
}
|
||||
if n.element.Section == "item list" {
|
||||
for _, v := range n.values {
|
||||
appendID(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(n.values) > 0 {
|
||||
shopIssue(doc, "sell item list", "商品列表包含无法识别的直接值")
|
||||
}
|
||||
for _, record := range n.children {
|
||||
if record.element.Section != "item" {
|
||||
shopIssue(doc, "sell item list", "商品列表包含无法识别的区块")
|
||||
continue
|
||||
}
|
||||
var index *shopNode
|
||||
for _, field := range record.children {
|
||||
if field.element.Section == "index" {
|
||||
if index != nil {
|
||||
shopIssue(doc, "index", "商品包含重复的 index 区块")
|
||||
}
|
||||
index = field
|
||||
}
|
||||
}
|
||||
if index == nil || len(index.values) != 1 {
|
||||
shopIssue(doc, "index", "商品缺少唯一的物品 ID")
|
||||
continue
|
||||
}
|
||||
// An index of -1 is an intentionally empty shop slot.
|
||||
if index.values[0].TokenType == 0 && index.values[0].Value == "-1" {
|
||||
continue
|
||||
}
|
||||
entry := appendID(index.values[0])
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
for _, field := range record.children {
|
||||
name := field.element.Section
|
||||
if name != "price" && name != "need material" {
|
||||
continue
|
||||
}
|
||||
if name == "price" {
|
||||
entry.localGold = true
|
||||
} else {
|
||||
entry.localMaterials = true
|
||||
}
|
||||
entry.HasLocalCosts = true
|
||||
entry.localCosts = append(entry.localCosts, shopCostValues(name, field.values, entry.Item.ID, doc)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user