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,147 @@
|
||||
package mod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type Extend110Writer struct{}
|
||||
|
||||
func (Extend110Writer) Files(pkg Package) []OutputFile {
|
||||
files := []OutputFile{{Path: "pack.json"}}
|
||||
for _, e := range pkg.Entries {
|
||||
dir := "pvf"
|
||||
if e.Operation != ReplaceFile {
|
||||
dir = "merge"
|
||||
}
|
||||
files = append(files, OutputFile{Path: dir + "/" + e.Path, EntryPath: e.Path})
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (Extend110Writer) Validate(pkg Package) error {
|
||||
return validateTextPackage(pkg, "110USextend")
|
||||
}
|
||||
|
||||
func validateTextPackage(pkg Package, format string) error {
|
||||
if err := ValidateName(pkg.Metadata.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidateVersion(pkg.Metadata.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateTextEntries(pkg.Entries, format)
|
||||
}
|
||||
|
||||
func validateTextEntries(entries []Entry, format string) error {
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("没有可应用的 mod 内容")
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
for _, e := range entries {
|
||||
if err := ValidatePath(e.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
key := strings.ToLower(e.Path)
|
||||
if seen[key] {
|
||||
return fmt.Errorf("mod 路径冲突: %s", e.Path)
|
||||
}
|
||||
seen[key] = true
|
||||
switch e.Operation {
|
||||
case ReplaceFile:
|
||||
if e.Encoding != Text || (e.DataType != 1 && e.DataType != 3) || !utf8.Valid(e.Data) || strings.HasPrefix(string(e.Data), "\ufeff") {
|
||||
return fmt.Errorf("%s 不支持文件的内容类型或编码: %s", format, e.Path)
|
||||
}
|
||||
case MergeList, MergeStrings:
|
||||
want := ".lst"
|
||||
if e.Operation == MergeStrings {
|
||||
want = ".str"
|
||||
}
|
||||
if !strings.EqualFold(filepath.Ext(e.Path), want) || len(e.Pairs) == 0 {
|
||||
return fmt.Errorf("无效的合并文件: %s", e.Path)
|
||||
}
|
||||
keys := make(map[string]bool)
|
||||
for _, pair := range e.Pairs {
|
||||
if pair.Key == "" || strings.ContainsAny(pair.Key+pair.Value, "\r\n\x00") || !utf8.ValidString(pair.Key+pair.Value) || keys[pair.Key] {
|
||||
return fmt.Errorf("无效或重复的合并条目: %s / %s", e.Path, pair.Key)
|
||||
}
|
||||
keys[pair.Key] = true
|
||||
if e.Operation == MergeList {
|
||||
n, err := strconv.ParseUint(pair.Key, 10, 32)
|
||||
if err != nil || strconv.FormatUint(n, 10) != pair.Key || strings.Contains(pair.Value, "`") {
|
||||
return fmt.Errorf("%s 列表编号或路径无法表达: %s / %s", format, e.Path, pair.Key)
|
||||
}
|
||||
if err := ValidatePath(pair.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.Contains(pair.Key, ">") || strings.HasPrefix(pair.Key, "//") || strings.HasPrefix(pair.Key, "\ufeff") {
|
||||
return fmt.Errorf("%s 文字表键无法表达: %s / %s", format, e.Path, pair.Key)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%s 不支持操作 %s: %s", format, e.Operation, e.Path)
|
||||
}
|
||||
}
|
||||
// Detect file/directory collisions too, including across pvf and merge.
|
||||
for key := range seen {
|
||||
for p := key; strings.Contains(p, "/"); {
|
||||
p = p[:strings.LastIndex(p, "/")]
|
||||
if seen[p] {
|
||||
return fmt.Errorf("mod 文件与目录路径冲突: %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w Extend110Writer) Write(root string, pkg Package) error {
|
||||
if err := w.Validate(pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
types := make(map[string]int32)
|
||||
for _, e := range pkg.Entries {
|
||||
dir, data := "pvf", e.Data
|
||||
if e.Operation != ReplaceFile {
|
||||
dir = "merge"
|
||||
var text strings.Builder
|
||||
for _, pair := range e.Pairs {
|
||||
if e.Operation == MergeList {
|
||||
fmt.Fprintf(&text, "%s\t`%s`\n", pair.Key, pair.Value)
|
||||
} else {
|
||||
fmt.Fprintf(&text, "%s>%s\n", pair.Key, pair.Value)
|
||||
}
|
||||
}
|
||||
data = []byte(text.String())
|
||||
} else {
|
||||
types[e.Path] = e.DataType
|
||||
}
|
||||
dest := filepath.Join(root, dir, filepath.FromSlash(e.Path))
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
version := pkg.Metadata.Version
|
||||
if version == "" {
|
||||
version = "1.0"
|
||||
}
|
||||
manifest := struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Priority int `json:"priority"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Types map[string]int32 `json:"types"`
|
||||
}{pkg.Metadata.Name, version, 100, true, types}
|
||||
data, err := json.MarshalIndent(manifest, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filepath.Join(root, "pack.json"), append(data, '\n'), 0o644)
|
||||
}
|
||||
Reference in new issue
Block a user