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,220 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"pvfine/internal/pvf"
|
||||
)
|
||||
|
||||
func annotationPosition(lineStarts []int, offset int) map[string]int {
|
||||
offset = max(0, offset)
|
||||
line := sort.Search(len(lineStarts), func(i int) bool { return lineStarts[i] > offset }) - 1
|
||||
return map[string]int{"line": line + 1, "column": offset - lineStarts[line] + 1}
|
||||
}
|
||||
func (s *MCPService) annotationExplain(a annotationArgs) (any, error) {
|
||||
if err := s.checkLocked(mcpArgs{WorkspaceID: a.WorkspaceID, ExpectedRevision: a.ExpectedRevision}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.Occurrence != nil && a.Section == "" || a.TokenIndex != nil && (a.Section == "" || a.Occurrence == nil) || a.StartLine != nil && a.EndLine != nil && *a.StartLine > *a.EndLine {
|
||||
return nil, fmt.Errorf("INVALID_ARGUMENT: occurrence/tokenIndex/行范围")
|
||||
}
|
||||
cat, err := s.c.annotationCatalogLocked()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.RulesRevision != "" && a.RulesRevision != cat.Revision {
|
||||
return nil, fmt.Errorf("STALE_RULES")
|
||||
}
|
||||
c := s.c
|
||||
index, ok := c.archive.Find(a.FilePath)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("FILE_NOT_FOUND")
|
||||
}
|
||||
if c.archive.File(index).DataSize > 8<<20 {
|
||||
return nil, fmt.Errorf("FILE_TOO_LARGE")
|
||||
}
|
||||
text, err := c.archive.Text(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text = strings.ReplaceAll(strings.ReplaceAll(text, "\r\n", "\n"), "\r", "\n")
|
||||
textRevision := annotationHash(text)
|
||||
rendered, err := c.editorAnnotationsLocked(index, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view := pvf.ParseScriptViewWithNestedSections(text, func(parent, child string) bool {
|
||||
if c.renderingEngine == nil {
|
||||
return false
|
||||
}
|
||||
for _, nested := range c.renderingEngine.SectionFormat(a.FilePath, parent).NestedSections {
|
||||
if strings.EqualFold(nested, child) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
anchors := c.annotationEngine.DocumentationAnchors(a.FilePath, view)
|
||||
anchorRanges := map[[2]int][]int{}
|
||||
for i, a := range anchors {
|
||||
key := [2]int{a.Start, a.End}
|
||||
anchorRanges[key] = append(anchorRanges[key], i)
|
||||
}
|
||||
docIDs := func(ids []string) []string {
|
||||
out := []string{}
|
||||
for _, id := range ids {
|
||||
for _, d := range cat.Items {
|
||||
want := d.Rule.ID
|
||||
if d.Entry.Kind == "field" {
|
||||
want = "field:" + want
|
||||
}
|
||||
if want == id && d.Entry.Effective && annotationVersion(d, cat.Document, c.archive.ClientVersion()) {
|
||||
out = append(out, d.DocumentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
pathContext := []map[string]any{}
|
||||
scope := a.FilePath
|
||||
directory := false
|
||||
for scope != "." && scope != "" {
|
||||
for _, r := range c.annotationEngine.AnnotatePath(scope, directory) {
|
||||
pathContext = append(pathContext, map[string]any{"scopePath": scope, "pathKind": map[bool]string{true: "directory", false: "file"}[directory], "title": r.Title, "content": r.Content, "type": r.Type, "ruleIds": r.RuleIDs, "documentIds": docIDs(r.RuleIDs)})
|
||||
}
|
||||
scope = path.Dir(scope)
|
||||
directory = true
|
||||
}
|
||||
occurrences := map[int]int{}
|
||||
counts := map[string]int{}
|
||||
for _, e := range view.Elements {
|
||||
if e.Kind == pvf.ScriptElementSection {
|
||||
key := annotationSection(e.Section)
|
||||
occurrences[e.SectionID] = counts[key]
|
||||
counts[key]++
|
||||
}
|
||||
}
|
||||
units := utf16.Encode([]rune(text))
|
||||
lineStarts := []int{0}
|
||||
for i, u := range units {
|
||||
if u == '\n' {
|
||||
lineStarts = append(lineStarts, i+1)
|
||||
}
|
||||
}
|
||||
items := []map[string]any{}
|
||||
for _, r := range rendered {
|
||||
start, end := int(r.Start), int(r.End)
|
||||
var element *pvf.ScriptElement
|
||||
values := []string{}
|
||||
elementStart := sort.Search(len(view.Elements), func(i int) bool { return view.Elements[i].End > start })
|
||||
for i := elementStart; i < len(view.Elements) && view.Elements[i].Start < end; i++ {
|
||||
e := &view.Elements[i]
|
||||
if e.Start < end && e.End > start {
|
||||
if element == nil {
|
||||
element = e
|
||||
}
|
||||
if e.Kind == pvf.ScriptElementToken {
|
||||
values = append(values, e.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if a.Section != "" && (element == nil || annotationSection(element.Section) != annotationSection(a.Section)) {
|
||||
continue
|
||||
}
|
||||
if a.Occurrence != nil && (element == nil || occurrences[element.SectionID] != *a.Occurrence) {
|
||||
continue
|
||||
}
|
||||
if a.TokenIndex != nil {
|
||||
selected := false
|
||||
for _, e := range view.Elements {
|
||||
if e.Kind == pvf.ScriptElementToken && element != nil && e.SectionID == element.SectionID && e.Index == *a.TokenIndex && e.Start < end && e.End > start {
|
||||
selected = true
|
||||
}
|
||||
}
|
||||
if !selected {
|
||||
continue
|
||||
}
|
||||
}
|
||||
begin, finish := annotationPosition(lineStarts, start), annotationPosition(lineStarts, end)
|
||||
last := annotationPosition(lineStarts, max(start, end-1))
|
||||
if a.StartLine != nil && last["line"] < *a.StartLine || a.EndLine != nil && begin["line"] > *a.EndLine {
|
||||
continue
|
||||
}
|
||||
content, truncated := annotationShort(r.Content)
|
||||
origin := "rule"
|
||||
if len(r.RuleIDs) == 0 {
|
||||
origin = "runtime"
|
||||
}
|
||||
item := map[string]any{"range": map[string]any{"start": start, "end": end, "startPosition": begin, "endPosition": finish}, "title": r.Title, "content": content, "contentTruncated": truncated, "type": r.Type, "ruleIds": r.RuleIDs, "documentIds": docIDs(r.RuleIDs), "origin": origin, "values": values, "inlineImage": r.InlineImage}
|
||||
if element != nil {
|
||||
item["section"] = element.Section
|
||||
item["sectionPath"] = element.SectionPath
|
||||
item["occurrence"] = occurrences[element.SectionID]
|
||||
if element.Kind == pvf.ScriptElementToken {
|
||||
item["tokenIndex"] = element.Index
|
||||
}
|
||||
}
|
||||
details := []map[string]any{}
|
||||
for _, anchorIndex := range anchorRanges[[2]int{start, end}] {
|
||||
anchor := anchors[anchorIndex]
|
||||
spec := anchor.Rule.Annotation
|
||||
detail := map[string]any{"ruleId": anchor.Rule.ID, "value": anchor.Value}
|
||||
if label, found := spec.Values[anchor.Value]; found {
|
||||
detail["valueLabel"] = label
|
||||
}
|
||||
if spec.Relation != "" {
|
||||
ctx := anchor.Context
|
||||
relation, exists := c.annotationEngine.Relation(spec.Relation)
|
||||
if exists && relation.Kind == "contextual" && strings.EqualFold(path.Ext(a.FilePath), ".skl") && (ctx == "" || ctx == anchor.Value) {
|
||||
if source := c.annotationContextForSourceLocked(index, spec.Relation, relation); source != "" {
|
||||
ctx = source
|
||||
}
|
||||
}
|
||||
status := "missing"
|
||||
reference := map[string]any{"relation": spec.Relation, "id": anchor.Value, "context": ctx}
|
||||
if !exists {
|
||||
status = "unsupported"
|
||||
} else if relation.Kind == "contextual" && ctx == "" {
|
||||
status = "context_required"
|
||||
} else if resolved, found := c.resolveAnnotationReferenceContextLocked(spec.Relation, anchor.Value, ctx); found {
|
||||
status = "resolved"
|
||||
reference["name"] = resolved.Name
|
||||
reference["path"] = resolved.Path
|
||||
}
|
||||
reference["resolutionStatus"] = status
|
||||
detail["reference"] = reference
|
||||
}
|
||||
details = append(details, detail)
|
||||
}
|
||||
item["details"] = details
|
||||
if r.TargetFileIndex >= 0 {
|
||||
item["targetPath"] = c.archive.Path(r.TargetFileIndex)
|
||||
}
|
||||
if r.Image != nil {
|
||||
item["image"] = r.Image
|
||||
}
|
||||
if r.Placeholder != nil {
|
||||
item["placeholder"] = map[string]any{"key": r.Placeholder.Key, "fallback": r.Placeholder.Fallback, "missing": r.Placeholder.Missing}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
if a.Limit == 0 {
|
||||
a.Limit = 20
|
||||
}
|
||||
a.Limit = max(1, min(a.Limit, 100))
|
||||
cursor := a.Cursor
|
||||
a.Cursor = ""
|
||||
a.RulesRevision = cat.Revision
|
||||
a.Section = annotationSection(a.Section)
|
||||
binding := annotationHash(a)
|
||||
workspace := fmt.Sprintf("%s:%d", a.WorkspaceID, a.ExpectedRevision)
|
||||
start, end, err := annotationPage(cursor, cat.Revision, binding, workspace, textRevision, len(items), a.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"schemaVersion": 1, "rulesRevision": cat.Revision, "workspaceId": a.WorkspaceID, "revision": a.ExpectedRevision, "textRevision": textRevision, "filePath": a.FilePath, "contentSource": "workspace", "coordinateUnit": "UTF-16", "pathContext": pathContext, "annotations": items[start:end], "nextCursor": annotationNext(end, len(items), cat.Revision, binding, workspace, textRevision), "truncated": end < len(items), "warnings": []string{}}, nil
|
||||
}
|
||||
Reference in new issue
Block a user