Files
90CNServer/pvf/internal/pvf/script_view.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

331 lines
8.2 KiB
Go

package pvf
import (
"math"
"strconv"
"strings"
)
const (
ScriptElementSection = "section"
ScriptElementToken = "token"
)
// ScriptView is a tolerant semantic projection of the exact editor text.
// Start and End are JavaScript UTF-16 code-unit offsets after normalizing line
// endings the same way as the editor (CRLF and CR each occupy one unit).
type ScriptView struct {
Text string
Elements []ScriptElement
}
// ScriptElement describes one opening section tag or one direct value token.
type ScriptElement struct {
Kind string
TokenType int32
Value string
Section string
SectionID int
SectionPath []string
Index int
Start int
End int
}
type scriptLexeme struct {
isSection bool
closing bool
name string
tokenType int32
value string
start int
end int
}
type semanticSection struct {
id int
name string
path []string
paired bool
tokenCount int
}
// ParseScriptView parses decompiled or edited TypeScript text without changing
// its formatting. Malformed fragments are skipped in the same tolerant spirit
// as encodeScript, while valid tokens retain their exact editor positions.
func ParseScriptView(text string) ScriptView {
return ParseScriptViewWithNestedSections(text, nil)
}
// ParseScriptViewWithNestedSections preserves an unpaired parent while parsing
// paired child sections explicitly declared by the active rendering rules.
func ParseScriptViewWithNestedSections(text string, isNested func(parent, child string) bool) ScriptView {
lexemes := lexScriptText(text)
pairedNames := make(map[string]bool)
for _, lexeme := range lexemes {
if lexeme.isSection && lexeme.closing {
pairedNames[lexeme.name] = true
}
}
elements := make([]ScriptElement, 0, len(lexemes))
sections := make([]semanticSection, 0)
nextSectionID := 1
for _, lexeme := range lexemes {
if lexeme.isSection {
if lexeme.closing {
for len(sections) > 0 && !sections[len(sections)-1].paired {
sections = sections[:len(sections)-1]
}
for i := len(sections) - 1; i >= 0; i-- {
if sections[i].paired && sections[i].name == lexeme.name {
sections = sections[:i]
break
}
}
continue
}
for len(sections) > 0 && !sections[len(sections)-1].paired {
parent := sections[len(sections)-1].name
if pairedNames[lexeme.name] && isNested != nil && isNested(parent, lexeme.name) {
break
}
sections = sections[:len(sections)-1]
}
sectionPath := make([]string, 0, len(sections)+1)
if len(sections) > 0 {
sectionPath = append(sectionPath, sections[len(sections)-1].path...)
}
sectionPath = append(sectionPath, lexeme.name)
sectionID := nextSectionID
nextSectionID++
elements = append(elements, ScriptElement{
Kind: ScriptElementSection,
TokenType: 3,
Value: lexeme.name,
Section: lexeme.name,
SectionID: sectionID,
SectionPath: append([]string(nil), sectionPath...),
Index: -1,
Start: lexeme.start,
End: lexeme.end,
})
sections = append(sections, semanticSection{
id: sectionID,
name: lexeme.name,
path: sectionPath,
paired: pairedNames[lexeme.name],
})
continue
}
element := ScriptElement{
Kind: ScriptElementToken,
TokenType: lexeme.tokenType,
Value: lexeme.value,
Index: -1,
Start: lexeme.start,
End: lexeme.end,
}
if len(sections) > 0 {
current := &sections[len(sections)-1]
element.Section = current.name
element.SectionID = current.id
element.SectionPath = append([]string(nil), current.path...)
element.Index = current.tokenCount
current.tokenCount++
}
elements = append(elements, element)
}
return ScriptView{Text: text, Elements: elements}
}
func lexScriptText(text string) []scriptLexeme {
rs := []rune(text)
offsets := editorOffsets(rs)
isWS := func(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r' || r == '\v' || r == '\f'
}
lexemes := make([]scriptLexeme, 0)
for i := 0; i < len(rs); {
if isWS(rs[i]) {
i++
continue
}
if rs[i] == '/' && i+1 < len(rs) {
if rs[i+1] == '/' {
for i < len(rs) && rs[i] != '\n' {
i++
}
continue
}
if rs[i+1] == '*' {
i += 2
for i+1 < len(rs) && !(rs[i] == '*' && rs[i+1] == '/') {
i++
}
i = min(i+2, len(rs))
continue
}
}
if rs[i] == '#' {
for i < len(rs) && rs[i] != '\n' {
i++
}
continue
}
if rs[i] == '`' {
start := i
value, next, _ := readBacktickString(rs, i)
lexemes = append(lexemes, scriptLexeme{
tokenType: 6,
value: value,
start: offsets[start],
end: offsets[next],
})
i = next
continue
}
if rs[i] == '{' {
start := i
if end := findMarkerEnd(rs, i+1); end >= 0 {
if typ, value, ok := parseTextMarker(string(rs[start : end+1])); ok {
lexemes = append(lexemes, scriptLexeme{
tokenType: typ,
value: value,
start: offsets[start],
end: offsets[end+1],
})
i = end + 1
continue
}
}
i++
continue
}
if rs[i] == '[' {
start := i
if end := indexRune(rs, i+1, ']'); end > i {
tag := string(rs[start : end+1])
name, closing, ok := parseSectionTag(tag)
lexemes = append(lexemes, scriptLexeme{
isSection: ok,
closing: closing,
name: name,
tokenType: 3,
value: tag,
start: offsets[start],
end: offsets[end+1],
})
i = end + 1
continue
}
i++
continue
}
start := i
for i < len(rs) && !isWS(rs[i]) && rs[i] != '`' && rs[i] != '{' && rs[i] != '[' {
i++
}
if i == start {
i++
continue
}
raw := string(rs[start:i])
typ, value := normalizeTextToken(raw)
lexemes = append(lexemes, scriptLexeme{
tokenType: typ,
value: value,
start: offsets[start],
end: offsets[i],
})
}
return lexemes
}
// editorOffsets maps offsets in the original Go string to offsets in the
// editor's normalized JavaScript string. CodeMirror treats CRLF and lone CR
// as a single line break, so the carriage return in a CRLF pair contributes
// no additional code unit.
func editorOffsets(rs []rune) []int {
offsets := make([]int, len(rs)+1)
for i, r := range rs {
if r == '\r' && i+1 < len(rs) && rs[i+1] == '\n' {
offsets[i+1] = offsets[i]
continue
}
offsets[i+1] = offsets[i] + utf16RuneLen(r)
}
return offsets
}
// parseTextMarker parses `{N=`...`}` block-value markers, where N is 5/7 for
// the 90US token streams and 8/10 for the newer Paged110 ones.
func parseTextMarker(marker string) (int32, string, bool) {
if len(marker) < 4 || marker[0] != '{' || marker[len(marker)-1] != '}' {
return 0, "", false
}
inner := marker[1 : len(marker)-1]
eq := strings.IndexByte(inner, '=')
if eq <= 0 {
return 0, "", false
}
n, err := strconv.Atoi(inner[:eq])
if err != nil || (n != 5 && n != 7 && n != 8 && n != 10) {
return 0, "", false
}
typ := int32(n)
inner = strings.TrimSpace(inner[eq+1:])
innerRunes := []rune(inner)
if value, next, ok := readBacktickString(innerRunes, 0); ok && next == len(innerRunes) {
return typ, value, true
}
return typ, inner, true
}
func normalizeTextToken(raw string) (int32, string) {
if _, _, ok := ParsePlaceholder(raw); ok {
return 8, raw
}
if value, err := strconv.ParseInt(raw, 10, 32); err == nil {
return 0, strconv.FormatInt(value, 10)
}
if value, err := strconv.ParseFloat(raw, 32); err == nil {
return 2, strconv.FormatFloat(float64(float32(value)), 'g', -1, 32)
}
return 3, raw
}
func utf16RuneLen(r rune) int {
if r > math.MaxUint16 {
return 2
}
return 1
}
// ScriptSectionClosingOffsets maps opening tags to their matching closing tag
// positions in editor UTF-16 units. The lexer excludes comments and strings.
func ScriptSectionClosingOffsets(text, section string) map[int]int {
result := map[int]int{}
stack := []int{}
for _, lexeme := range lexScriptText(text) {
if !lexeme.isSection || lexeme.name != section {
continue
}
if !lexeme.closing {
stack = append(stack, lexeme.start)
continue
}
if len(stack) == 0 {
continue
}
result[stack[len(stack)-1]] = lexeme.start
stack = stack[:len(stack)-1]
}
return result
}