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,150 @@
|
||||
package worldmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
)
|
||||
|
||||
// mapDocumentLoader resolves an existing map document. The caller keeps the
|
||||
// source-specific path lookup out of the import parser so Load and LoadSource
|
||||
// share the same PVF import semantics.
|
||||
type mapDocumentLoader func(candidate string) (string, *dnfpvf.Document, error)
|
||||
|
||||
func parseMapWithImports(id int64, docPath string, doc *dnfpvf.Document, load mapDocumentLoader) (Map, error) {
|
||||
if doc == nil {
|
||||
return Map{}, fmt.Errorf("map document %q is nil", docPath)
|
||||
}
|
||||
if load == nil {
|
||||
return Map{}, fmt.Errorf("map document loader is nil")
|
||||
}
|
||||
|
||||
cache := make(map[string]Map)
|
||||
visiting := make(map[string]struct{})
|
||||
var parse func(string, *dnfpvf.Document) (Map, error)
|
||||
parse = func(currentPath string, currentDoc *dnfpvf.Document) (Map, error) {
|
||||
key := pathKey(currentPath)
|
||||
if parsed, ok := cache[key]; ok {
|
||||
return cloneMap(parsed), nil
|
||||
}
|
||||
if _, exists := visiting[key]; exists {
|
||||
return Map{}, fmt.Errorf("cyclic map import at %q", currentPath)
|
||||
}
|
||||
visiting[key] = struct{}{}
|
||||
defer delete(visiting, key)
|
||||
|
||||
parsed := ParseMap(0, currentPath, currentDoc)
|
||||
for _, reference := range mapImportReferences(currentDoc) {
|
||||
importPath, importDoc, err := loadMapImport(currentPath, reference, load)
|
||||
if err != nil {
|
||||
return Map{}, err
|
||||
}
|
||||
imported, err := parse(importPath, importDoc)
|
||||
if err != nil {
|
||||
return Map{}, err
|
||||
}
|
||||
parsed = mergeImportedMapStaticSpawns(parsed, imported)
|
||||
}
|
||||
cache[key] = cloneMap(parsed)
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
parsed, err := parse(docPath, doc)
|
||||
if err != nil {
|
||||
return Map{}, err
|
||||
}
|
||||
parsed.ID = id
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func mapImportReferences(doc *dnfpvf.Document) []string {
|
||||
sections, _ := rawSections(doc)
|
||||
var out []string
|
||||
for _, section := range sections {
|
||||
if sectionKey(section.Name) != "import script" {
|
||||
continue
|
||||
}
|
||||
for _, reference := range texts(section) {
|
||||
reference = strings.TrimSpace(reference)
|
||||
if reference != "" {
|
||||
out = append(out, reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func loadMapImport(ownerPath, reference string, load mapDocumentLoader) (string, *dnfpvf.Document, error) {
|
||||
var lastErr error
|
||||
for _, candidate := range mapImportCandidates(ownerPath, reference) {
|
||||
resolvedPath, doc, err := load(candidate)
|
||||
if err == nil && doc != nil {
|
||||
return resolvedPath, doc, nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
if lastErr == nil {
|
||||
lastErr = fmt.Errorf("no matching document")
|
||||
}
|
||||
return "", nil, fmt.Errorf("resolve map import %q from %q: %w", reference, ownerPath, lastErr)
|
||||
}
|
||||
|
||||
func mapImportCandidates(ownerPath, reference string) []string {
|
||||
reference = strings.TrimSpace(strings.ReplaceAll(reference, "\\", "/"))
|
||||
for strings.HasPrefix(reference, "./") || strings.HasPrefix(reference, "/") {
|
||||
reference = strings.TrimPrefix(reference, "./")
|
||||
reference = strings.TrimPrefix(reference, "/")
|
||||
}
|
||||
if reference == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ownerPath = strings.TrimSpace(strings.ReplaceAll(ownerPath, "\\", "/"))
|
||||
var candidates []string
|
||||
add := func(candidate string) {
|
||||
candidate = strings.TrimPrefix(path.Clean(candidate), "./")
|
||||
if candidate == "." || candidate == "" {
|
||||
return
|
||||
}
|
||||
for _, existing := range candidates {
|
||||
if pathKey(existing) == pathKey(candidate) {
|
||||
return
|
||||
}
|
||||
}
|
||||
candidates = append(candidates, candidate)
|
||||
}
|
||||
add(reference)
|
||||
add(path.Join(path.Dir(ownerPath), reference))
|
||||
|
||||
parts := strings.Split(ownerPath, "/")
|
||||
for index, part := range parts {
|
||||
if !strings.EqualFold(part, "map") {
|
||||
continue
|
||||
}
|
||||
add(path.Join(strings.Join(parts[:index+1], "/"), reference))
|
||||
break
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
// Imported town maps commonly carry the physical NPC and passive-object
|
||||
// placements shared by several maps. Keep the root map metadata authoritative
|
||||
// while exposing those real imported placements to the runtime catalog.
|
||||
func mergeImportedMapStaticSpawns(base, imported Map) Map {
|
||||
imported = cloneMap(imported)
|
||||
base.PathgatePositions = append(imported.PathgatePositions, base.PathgatePositions...)
|
||||
base.PathgateObjects = append(imported.PathgateObjects, base.PathgateObjects...)
|
||||
base.Portals = buildPortals(base.PathgatePositions, base.PathgateObjects)
|
||||
base.AnimationObjects = append(imported.AnimationObjects, base.AnimationObjects...)
|
||||
base.PassiveObjects = append(imported.PassiveObjects, base.PassiveObjects...)
|
||||
base.SpecialPassiveObjects = append(imported.SpecialPassiveObjects, base.SpecialPassiveObjects...)
|
||||
base.NPCs = append(imported.NPCs, base.NPCs...)
|
||||
base.AICharacters = append(imported.AICharacters, base.AICharacters...)
|
||||
base.TownMovableAreas = append(imported.TownMovableAreas, base.TownMovableAreas...)
|
||||
base.DungeonMovableAreas = append(imported.DungeonMovableAreas, base.DungeonMovableAreas...)
|
||||
return base
|
||||
}
|
||||
Reference in new issue
Block a user