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,271 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
annotationrules "pvfine/internal/annotations"
|
||||
"pvfine/internal/buildmode"
|
||||
"pvfine/internal/ruleconfig"
|
||||
)
|
||||
|
||||
// RuleService exposes only rule identities and contents, never arbitrary paths.
|
||||
type RuleService struct{ c *core }
|
||||
|
||||
func NewRuleService(c *core) *RuleService { return &RuleService{c: c} }
|
||||
|
||||
type RuleTarget struct {
|
||||
Category string `json:"category"`
|
||||
Path string `json:"path"`
|
||||
Section string `json:"section"`
|
||||
Directory bool `json:"directory"`
|
||||
}
|
||||
type RuleQueryResult struct {
|
||||
Revision string `json:"revision"`
|
||||
Development bool `json:"development"`
|
||||
Entries []ruleconfig.Entry `json:"entries"`
|
||||
Relations []string `json:"relations"`
|
||||
}
|
||||
|
||||
func (s *RuleService) StartupWarnings() []string { return append([]string{}, s.c.ruleWarnings...) }
|
||||
func (s *RuleService) Query(target RuleTarget) (RuleQueryResult, error) {
|
||||
if s.c.ruleStore == nil {
|
||||
return RuleQueryResult{}, fmt.Errorf("规则存储不可用")
|
||||
}
|
||||
snap, err := s.c.ruleStore.Load(target.Category)
|
||||
if err != nil {
|
||||
return RuleQueryResult{}, err
|
||||
}
|
||||
s.c.mu.RLock()
|
||||
version := s.c.archive.ClientVersion()
|
||||
archiveOpen := s.c.archive != nil
|
||||
s.c.mu.RUnlock()
|
||||
result := RuleQueryResult{Revision: snap.Revision, Development: snap.Development, Entries: []ruleconfig.Entry{}, Relations: []string{}}
|
||||
ann, renderer, err := snap.Engines(target.Category)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
applicable := map[string]bool{}
|
||||
activeFields := map[string]bool{}
|
||||
if ann != nil {
|
||||
if archiveOpen {
|
||||
ann = ann.ForVersion(version)
|
||||
}
|
||||
applicable = ann.ApplicableRuleIDs(target.Path, target.Section, target.Directory)
|
||||
for _, field := range ann.Document().Fields {
|
||||
if field.Preview != nil {
|
||||
activeFields[field.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
fields := map[string]json.RawMessage{}
|
||||
for _, entry := range snap.Entries {
|
||||
if entry.Kind == "field" {
|
||||
fields[entry.ID] = json.RawMessage(entry.JSON)
|
||||
}
|
||||
}
|
||||
for _, entry := range snap.Entries {
|
||||
var r struct {
|
||||
Field string `json:"field"`
|
||||
PVFVersions []string `json:"pvfVersions"`
|
||||
Match annotationrules.MatchSpec `json:"match"`
|
||||
Target annotationrules.TargetSpec `json:"target"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(entry.JSON), &r); err != nil {
|
||||
return result, err
|
||||
}
|
||||
if r.Field != "" {
|
||||
var f struct {
|
||||
Match annotationrules.MatchSpec `json:"match"`
|
||||
Target annotationrules.TargetSpec `json:"target"`
|
||||
}
|
||||
_ = json.Unmarshal(fields[r.Field], &f)
|
||||
if r.Target.Kind == "" {
|
||||
r.Target = f.Target
|
||||
}
|
||||
if r.Match.Glob == "" && len(r.Match.Extensions) == 0 {
|
||||
r.Match = f.Match
|
||||
}
|
||||
}
|
||||
// A directory rendering entry describes descendant files; use the rule's
|
||||
// glob plus a representative child for extension-only rules.
|
||||
matches := ruleconfig.Matches(r.Match.Glob, target.Path, r.Match.Extensions)
|
||||
if target.Directory && target.Category == "rendering" {
|
||||
matches = false
|
||||
candidates := []string{target.Path + "/example"}
|
||||
for _, extension := range r.Match.Extensions {
|
||||
candidates = append(candidates, target.Path+"/example"+extension)
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
matches = matches || ruleconfig.Matches(r.Match.Glob, candidate, r.Match.Extensions)
|
||||
}
|
||||
prefix := strings.TrimSuffix(target.Path, "/") + "/"
|
||||
matches = matches || strings.HasPrefix(strings.ToLower(r.Match.Glob), strings.ToLower(prefix))
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
if target.Section != "" {
|
||||
if !strings.EqualFold(r.Target.Section, target.Section) {
|
||||
continue
|
||||
}
|
||||
} else if target.Category == "annotations" && r.Target.Kind != "path" {
|
||||
continue
|
||||
}
|
||||
if target.Directory && target.Category == "annotations" && len(r.Match.Extensions) > 0 {
|
||||
continue
|
||||
}
|
||||
if len(r.PVFVersions) > 0 && version != "" {
|
||||
ok := false
|
||||
for _, v := range r.PVFVersions {
|
||||
ok = ok || v == version
|
||||
}
|
||||
entry.Effective = entry.Effective && ok
|
||||
}
|
||||
if ann != nil {
|
||||
id := entry.ID
|
||||
if entry.Kind == "field" {
|
||||
id = "field:" + id
|
||||
}
|
||||
entry.Effective = entry.Effective && (applicable[id] || (entry.Kind == "field" && activeFields[entry.ID]))
|
||||
}
|
||||
if renderer != nil && !target.Directory {
|
||||
chosen := renderer.EffectiveRuleID(target.Path, r.Target.Section, r.Target.Kind)
|
||||
entry.Effective = entry.Effective && chosen == entry.ID
|
||||
}
|
||||
result.Entries = append(result.Entries, entry)
|
||||
}
|
||||
if ann != nil {
|
||||
for name := range ann.Document().Relations {
|
||||
result.Relations = append(result.Relations, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(result.Relations)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Apply validates, saves and activates a rule operation. A stale revision is rejected.
|
||||
func (s *RuleService) Apply(m ruleconfig.Mutation) error {
|
||||
if s.c.ruleStore == nil {
|
||||
return fmt.Errorf("规则存储不可用")
|
||||
}
|
||||
// Serialize disk mutation and engine activation, including concurrent reloads.
|
||||
s.c.ruleMu.Lock()
|
||||
defer s.c.ruleMu.Unlock()
|
||||
snap, err := s.c.ruleStore.Mutate(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.activate(m.Category, snap)
|
||||
}
|
||||
func (s *RuleService) reload(category string) error {
|
||||
s.c.ruleMu.Lock()
|
||||
defer s.c.ruleMu.Unlock()
|
||||
snap, err := s.c.ruleStore.Load(category)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.activate(category, snap)
|
||||
}
|
||||
func (s *RuleService) activate(category string, snap *ruleconfig.Snapshot) error {
|
||||
ann, renderer, err := snap.Engines(category)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := s.c
|
||||
c.mu.Lock()
|
||||
archiveOpen := c.archive != nil
|
||||
specChanged := false
|
||||
if ann != nil {
|
||||
old := searchIndexSpecFingerprint(c.searchableListSpecsLocked())
|
||||
c.annotationSnapshot = snap
|
||||
c.annotationEngine = ann.ForVersion(c.archive.ClientVersion())
|
||||
c.annotationErr = nil
|
||||
c.annotationRelations = make(map[string]map[string]*relationTarget)
|
||||
c.pathAnnotations = nil
|
||||
if archiveOpen {
|
||||
c.pathAnnotations = buildPathAnnotations(c.annotationEngine, c.dirChildren)
|
||||
}
|
||||
specChanged = old != searchIndexSpecFingerprint(c.searchableListSpecsLocked())
|
||||
if archiveOpen && specChanged {
|
||||
c.markAdvancedSearchStaleLocked(c.archive)
|
||||
}
|
||||
if archiveOpen && specChanged && c.diskIndex != nil {
|
||||
c.diskIndex.dirty = true
|
||||
c.diskIndex.semanticEpoch++
|
||||
}
|
||||
}
|
||||
if renderer != nil {
|
||||
c.renderingEngine = renderer
|
||||
c.renderingErr = nil
|
||||
if archiveOpen {
|
||||
c.bindRenderingEngineLocked(c.archive)
|
||||
}
|
||||
if c.versionBaseArchive != nil {
|
||||
c.versionBaseArchive.SetScriptRenderer(renderer)
|
||||
}
|
||||
c.batchRevision++
|
||||
c.batchPlan = nil
|
||||
c.invalidateScriptLocked()
|
||||
}
|
||||
c.editorAnnotation = editorAnnotationCache{}
|
||||
c.mu.Unlock()
|
||||
if archiveOpen && specChanged {
|
||||
c.startSearchIndex()
|
||||
}
|
||||
if ann != nil {
|
||||
doc := ann.Document()
|
||||
emitEvent("annotations:reloaded", AnnotationReloadResult{RuleCount: len(doc.Rules), RelationCount: len(doc.Relations)})
|
||||
}
|
||||
if renderer != nil {
|
||||
emitEvent("rendering:reloaded", RenderingReloadResult{RuleCount: len(renderer.Document().Rules)})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *core) initializeRuleStore() {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
c.ruleWarnings = append(c.ruleWarnings, err.Error())
|
||||
return
|
||||
}
|
||||
sourceDir := ""
|
||||
if buildmode.Development {
|
||||
if source, ok := annotationrules.FindSourcePath(); ok {
|
||||
sourceDir = filepath.Dir(source)
|
||||
}
|
||||
}
|
||||
c.loadInitialRules(ruleconfig.New(buildmode.Development, sourceDir, filepath.Join(configDir, "pvfine")))
|
||||
}
|
||||
|
||||
func (c *core) loadInitialRules(store *ruleconfig.Store) {
|
||||
c.ruleStore = store
|
||||
for _, category := range []string{"annotations", "rendering"} {
|
||||
if err := c.ruleStore.Migrate(category); err != nil {
|
||||
c.ruleWarnings = append(c.ruleWarnings, err.Error())
|
||||
continue
|
||||
}
|
||||
snap, err := c.ruleStore.Load(category)
|
||||
if err != nil {
|
||||
c.ruleWarnings = append(c.ruleWarnings, err.Error())
|
||||
continue
|
||||
}
|
||||
ann, renderer, err := snap.Engines(category)
|
||||
if err != nil {
|
||||
c.ruleWarnings = append(c.ruleWarnings, err.Error())
|
||||
continue
|
||||
}
|
||||
if ann != nil {
|
||||
c.annotationSnapshot = snap
|
||||
c.annotationEngine = ann
|
||||
c.annotationErr = nil
|
||||
}
|
||||
if renderer != nil {
|
||||
c.renderingEngine = renderer
|
||||
c.renderingErr = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user