Files
90CNServer/pvf/services/mcp_annotation_explain.go
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

221 lines
7.9 KiB
Go

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
}