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,223 @@
|
||||
package pvf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Parse 把单个 PVF 文本文档解析成通用 token 和 section。
|
||||
func Parse(docPath, text string) (*Document, error) {
|
||||
docPath = cleanPath(docPath)
|
||||
if docPath == "" {
|
||||
return nil, ErrPathRequired
|
||||
}
|
||||
parser := docParser{
|
||||
doc: Document{Path: docPath},
|
||||
}
|
||||
lines := strings.Split(text, "\n")
|
||||
for idx, line := range lines {
|
||||
if err := parser.parseLine(idx+1, line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if parser.pending != nil {
|
||||
return nil, fmt.Errorf("dnf pvf string at %d:%d is not closed", parser.pending.line, parser.pending.column)
|
||||
}
|
||||
parser.closeSection()
|
||||
return &parser.doc, nil
|
||||
}
|
||||
|
||||
type docParser struct {
|
||||
doc Document
|
||||
pending *pendingString
|
||||
}
|
||||
|
||||
type pendingString struct {
|
||||
quote byte
|
||||
line int
|
||||
column int
|
||||
raw strings.Builder
|
||||
value strings.Builder
|
||||
}
|
||||
|
||||
func (p *docParser) parseLine(lineNo int, line string) error {
|
||||
if p.pending != nil {
|
||||
return p.continueString(lineNo, line)
|
||||
}
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, "//") {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "[") {
|
||||
if end := strings.IndexByte(trimmed, ']'); end > 0 {
|
||||
column := strings.Index(line, "[") + 1
|
||||
name := strings.TrimSpace(trimmed[1:end])
|
||||
p.addToken(Token{
|
||||
Kind: TokenSection,
|
||||
Raw: trimmed[:end+1],
|
||||
Value: name,
|
||||
Line: lineNo,
|
||||
Column: column,
|
||||
})
|
||||
return p.scanValues(lineNo, column+end+1, trimmed[end+1:])
|
||||
}
|
||||
}
|
||||
column := len(line) - len(strings.TrimLeftFunc(line, unicode.IsSpace)) + 1
|
||||
return p.scanValues(lineNo, column, trimmed)
|
||||
}
|
||||
|
||||
func (p *docParser) addToken(token Token) {
|
||||
if token.Kind == TokenSection {
|
||||
p.closeSection()
|
||||
p.doc.Sections = append(p.doc.Sections, Section{
|
||||
Name: token.Value,
|
||||
Start: len(p.doc.Tokens) + 1,
|
||||
})
|
||||
}
|
||||
p.doc.Tokens = append(p.doc.Tokens, token)
|
||||
}
|
||||
|
||||
func (p *docParser) closeSection() {
|
||||
if len(p.doc.Sections) == 0 {
|
||||
return
|
||||
}
|
||||
idx := len(p.doc.Sections) - 1
|
||||
if p.doc.Sections[idx].End == 0 {
|
||||
p.doc.Sections[idx].End = len(p.doc.Tokens)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *docParser) scanValues(lineNo, baseColumn int, line string) error {
|
||||
for pos := 0; pos < len(line); {
|
||||
if isSpace(line[pos]) {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
if pos+1 < len(line) && line[pos] == '/' && line[pos+1] == '/' {
|
||||
return nil
|
||||
}
|
||||
if line[pos] == '#' {
|
||||
return nil
|
||||
}
|
||||
column := baseColumn + pos
|
||||
switch line[pos] {
|
||||
case '`', '\'', '"':
|
||||
token, next, complete := p.beginString(lineNo, column, line, pos)
|
||||
if !complete {
|
||||
return nil
|
||||
}
|
||||
p.addToken(token)
|
||||
pos = next
|
||||
case '{', '}', '=', ',', ':':
|
||||
p.addToken(Token{Kind: TokenSymbol, Raw: line[pos : pos+1], Value: line[pos : pos+1], Line: lineNo, Column: column})
|
||||
pos++
|
||||
default:
|
||||
raw, next := scanBare(line, pos)
|
||||
if raw == "" {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
p.addToken(parseBare(lineNo, column, raw))
|
||||
pos = next
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *docParser) beginString(lineNo, column int, line string, start int) (Token, int, bool) {
|
||||
pending := &pendingString{quote: line[start], line: lineNo, column: column}
|
||||
pending.raw.WriteByte(line[start])
|
||||
next, complete := appendStringSegment(pending, line, start+1)
|
||||
if !complete {
|
||||
p.pending = pending
|
||||
return Token{}, 0, false
|
||||
}
|
||||
return pending.token(), next, true
|
||||
}
|
||||
|
||||
func (p *docParser) continueString(lineNo int, line string) error {
|
||||
p.pending.raw.WriteByte('\n')
|
||||
p.pending.value.WriteByte('\n')
|
||||
next, complete := appendStringSegment(p.pending, line, 0)
|
||||
if !complete {
|
||||
return nil
|
||||
}
|
||||
token := p.pending.token()
|
||||
p.pending = nil
|
||||
p.addToken(token)
|
||||
return p.scanValues(lineNo, next+1, line[next:])
|
||||
}
|
||||
|
||||
func appendStringSegment(pending *pendingString, line string, start int) (int, bool) {
|
||||
for pos := start; pos < len(line); pos++ {
|
||||
value := line[pos]
|
||||
pending.raw.WriteByte(value)
|
||||
if value == pending.quote {
|
||||
return pos + 1, true
|
||||
}
|
||||
if pending.quote != '`' && value == '\\' && pos+1 < len(line) {
|
||||
pos++
|
||||
pending.raw.WriteByte(line[pos])
|
||||
pending.value.WriteByte(line[pos])
|
||||
continue
|
||||
}
|
||||
pending.value.WriteByte(value)
|
||||
}
|
||||
return len(line), false
|
||||
}
|
||||
|
||||
func (p *pendingString) token() Token {
|
||||
return Token{
|
||||
Kind: TokenString,
|
||||
Raw: p.raw.String(),
|
||||
Value: p.value.String(),
|
||||
Line: p.line,
|
||||
Column: p.column,
|
||||
}
|
||||
}
|
||||
|
||||
func scanBare(line string, start int) (string, int) {
|
||||
pos := start
|
||||
for pos < len(line) {
|
||||
if isSpace(line[pos]) || strings.ContainsRune("{},:=", rune(line[pos])) {
|
||||
break
|
||||
}
|
||||
if pos+1 < len(line) && line[pos] == '/' && line[pos+1] == '/' {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
}
|
||||
return line[start:pos], pos
|
||||
}
|
||||
|
||||
func parseBare(lineNo, column int, raw string) Token {
|
||||
if value, ok := parseInt(raw); ok {
|
||||
return Token{Kind: TokenInt, Raw: raw, Value: raw, Int: value, Line: lineNo, Column: column}
|
||||
}
|
||||
if value, ok := parseFloat(raw); ok {
|
||||
return Token{Kind: TokenFloat, Raw: raw, Value: raw, Float: value, Line: lineNo, Column: column}
|
||||
}
|
||||
return Token{Kind: TokenIdent, Raw: raw, Value: raw, Line: lineNo, Column: column}
|
||||
}
|
||||
|
||||
func parseInt(raw string) (int64, bool) {
|
||||
if strings.ContainsAny(raw, ".eE") {
|
||||
return 0, false
|
||||
}
|
||||
value, err := strconv.ParseInt(raw, 10, 64)
|
||||
return value, err == nil
|
||||
}
|
||||
|
||||
func parseFloat(raw string) (float64, bool) {
|
||||
if !strings.ContainsAny(raw, ".eE") {
|
||||
return 0, false
|
||||
}
|
||||
value, err := strconv.ParseFloat(raw, 64)
|
||||
return value, err == nil
|
||||
}
|
||||
|
||||
func isSpace(value byte) bool {
|
||||
return value == ' ' || value == '\t' || value == '\r'
|
||||
}
|
||||
Reference in new issue
Block a user