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

+197
View File
@@ -0,0 +1,197 @@
package rendering
import (
"path"
"strings"
)
// Engine is an immutable, compiled set of script rendering rules.
type Engine struct {
document Document
rules []compiledRule
}
type compiledRule struct {
rule Rule
extensions map[string]struct{}
specificity int
order int
}
// Compile validates and compiles a rendering rule document.
func Compile(document Document) (*Engine, error) {
if document.Rules == nil {
document.Rules = make([]Rule, 0)
}
if err := Validate(document); err != nil {
return nil, err
}
rules := make([]compiledRule, 0, len(document.Rules))
for order, rule := range document.Rules {
extensions := make(map[string]struct{}, len(rule.Match.Extensions))
for _, extension := range rule.Match.Extensions {
extensions[strings.ToLower(strings.TrimSpace(extension))] = struct{}{}
}
specificity := 0
if len(rule.Match.Extensions) > 0 {
specificity++
}
if strings.TrimSpace(rule.Match.Glob) != "" {
specificity++
}
rules = append(rules, compiledRule{
rule: rule,
extensions: extensions,
specificity: specificity,
order: order,
})
}
return &Engine{document: document, rules: rules}, nil
}
// Document returns the source document used to build the engine.
func (e *Engine) Document() Document {
if e == nil {
return Document{}
}
document := e.document
document.Rules = append([]Rule(nil), e.document.Rules...)
for index := range document.Rules {
if widths := document.Rules[index].Format.TokensPerLineByVersion; widths != nil {
cloned := make(map[string]int, len(widths))
for version, width := range widths {
cloned[version] = width
}
document.Rules[index].Format.TokensPerLineByVersion = cloned
}
document.Rules[index].Match.Extensions = append([]string(nil), document.Rules[index].Match.Extensions...)
document.Rules[index].Format.StandaloneValues = append([]int32(nil), document.Rules[index].Format.StandaloneValues...)
document.Rules[index].Format.NestedSections = append([]string(nil), document.Rules[index].Format.NestedSections...)
if value := document.Rules[index].Format.TokensPerLineIndex; value != nil {
copy := *value
document.Rules[index].Format.TokensPerLineIndex = &copy
}
}
return document
}
// FileFormat resolves the best file-scoped rule for filePath. A zero value
// means that the decoder should use its ordinary layout.
func (e *Engine) FileFormat(filePath string) FormatSpec {
return e.resolve(filePath, "", "file")
}
// SectionFormat resolves the best section-scoped rule for a section name.
// Section names are intentionally matched without considering nesting or
// occurrence, matching the annotation engine's V1 semantics.
func (e *Engine) SectionFormat(filePath, section string) FormatSpec {
return e.resolve(filePath, section, "section")
}
func (e *Engine) resolve(filePath, section, kind string) FormatSpec {
if e == nil {
return FormatSpec{}
}
bestSpecificity := -1
bestOrder := -1
var format FormatSpec
for _, compiled := range e.rules {
rule := compiled.rule
if rule.Target.Kind != kind || !compiled.matches(filePath) {
continue
}
if kind == "section" && !strings.EqualFold(strings.TrimSpace(rule.Target.Section), strings.TrimSpace(section)) {
continue
}
if compiled.specificity < bestSpecificity ||
(compiled.specificity == bestSpecificity && compiled.order < bestOrder) {
continue
}
bestSpecificity = compiled.specificity
bestOrder = compiled.order
format = compiled.rule.Format
}
return format
}
func (r compiledRule) matches(filePath string) bool {
filePath = normalizePath(filePath)
if len(r.extensions) > 0 {
if _, ok := r.extensions[strings.ToLower(path.Ext(filePath))]; !ok {
return false
}
}
glob := strings.TrimSpace(r.rule.Match.Glob)
return glob == "" || globMatch(glob, filePath)
}
func normalizePath(value string) string {
return strings.ToLower(strings.Trim(strings.ReplaceAll(value, "\\", "/"), "/"))
}
func globMatch(pattern, value string) bool {
patternParts := splitPath(normalizePath(pattern))
valueParts := splitPath(normalizePath(value))
type state struct{ pattern, value int }
memo := make(map[state]bool)
seen := make(map[state]bool)
var match func(int, int) bool
match = func(patternIndex, valueIndex int) bool {
key := state{pattern: patternIndex, value: valueIndex}
if seen[key] {
return memo[key]
}
seen[key] = true
if patternIndex == len(patternParts) {
memo[key] = valueIndex == len(valueParts)
return memo[key]
}
if patternParts[patternIndex] == "**" {
if match(patternIndex+1, valueIndex) ||
(valueIndex < len(valueParts) && match(patternIndex, valueIndex+1)) {
memo[key] = true
}
return memo[key]
}
if valueIndex >= len(valueParts) {
return false
}
matched, err := path.Match(patternParts[patternIndex], valueParts[valueIndex])
if err == nil && matched {
memo[key] = match(patternIndex+1, valueIndex+1)
}
return memo[key]
}
return match(0, 0)
}
func splitPath(value string) []string {
if value == "" {
return nil
}
return strings.Split(value, "/")
}
// EffectiveRuleID reports the selected rule for management UI diagnostics.
func (e *Engine) EffectiveRuleID(filePath, section, kind string) string {
if e == nil {
return ""
}
bestSpecificity, bestOrder := -1, -1
id := ""
for _, compiled := range e.rules {
r := compiled.rule
if r.Target.Kind != kind || !compiled.matches(filePath) {
continue
}
if kind == "section" && !strings.EqualFold(strings.TrimSpace(r.Target.Section), strings.TrimSpace(section)) {
continue
}
if compiled.specificity < bestSpecificity || (compiled.specificity == bestSpecificity && compiled.order < bestOrder) {
continue
}
bestSpecificity, bestOrder, id = compiled.specificity, compiled.order, r.ID
}
return id
}