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,205 @@
|
||||
package runtimeguard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 历史背景:本包之前只暴露 Check / CheckDescriber 这一对无状态函数;wiring 层
|
||||
// 要为每一个 runtime 模块(matchmaker、party、crossserver、未来还会更多)手动
|
||||
// 调用一次,谁漏调用谁就静默逃过门禁。
|
||||
//
|
||||
// 全局清单把"声明 — 校验"分成两步:
|
||||
// 1. 各 runtime 模块构造完成后立即调用 Register / RegisterDescriber,
|
||||
// 把模块名 + 后端类型挂进进程内全局清单。
|
||||
// 2. wiring 收尾前调用 Verify(profile) 一次性核对所有声明,违规项被合并
|
||||
// 成一个 multi-error 抛出,main 看到 error != nil 直接停机。
|
||||
//
|
||||
// 这样的好处:
|
||||
// - 加新的 runtime 模块不再需要 wiring 代码同步加一行 Check;
|
||||
// - 多个违规会一次性报出来,而不是修一条复活一条;
|
||||
// - 单元测试可以用 Reset 清空全局状态,避免污染。
|
||||
|
||||
// Registration 记录一次注册。Source 是可选的调用方标识(比如 main 的 file:line),
|
||||
// 主要用来在 Verify 失败时给 ops 一个"到底是谁声明的"的线索。
|
||||
type Registration struct {
|
||||
Module string
|
||||
Backend Backend
|
||||
Source string
|
||||
}
|
||||
|
||||
type Catalog struct {
|
||||
mu sync.RWMutex
|
||||
entries []Registration
|
||||
}
|
||||
|
||||
func NewCatalog() *Catalog {
|
||||
return &Catalog{}
|
||||
}
|
||||
|
||||
var defaultCatalog = NewCatalog()
|
||||
|
||||
// Register 把 module + backend 加进全局清单。生产代码调用,参数静态可见。
|
||||
// 重复注册(同 module)允许,最后一个生效;这样在 hot-reload 或重新装配场景
|
||||
// 下不会泄漏过时声明。
|
||||
func Register(module string, backend Backend) {
|
||||
defaultCatalog.Register(module, backend)
|
||||
}
|
||||
|
||||
// RegisterDescriber 是 Register 的便捷形式。describer 为 nil 视为 BackendUnknown。
|
||||
func RegisterDescriber(module string, describer BackendDescriber) {
|
||||
defaultCatalog.RegisterDescriber(module, describer)
|
||||
}
|
||||
|
||||
func (inv *Catalog) Register(module string, backend Backend) {
|
||||
if inv == nil {
|
||||
return
|
||||
}
|
||||
inv.registerInternal(Registration{Module: normalizeModule(module), Backend: backend})
|
||||
}
|
||||
|
||||
func (inv *Catalog) RegisterDescriber(module string, describer BackendDescriber) {
|
||||
var b Backend
|
||||
if describer != nil {
|
||||
b = describer.BackendKind()
|
||||
}
|
||||
inv.Register(module, b)
|
||||
}
|
||||
|
||||
// RegisterFromSource 与 Register 相同,但额外记录 caller 信息。
|
||||
// 推荐 wiring 层使用 "package.Function" 或文件路径作为 source。
|
||||
func RegisterFromSource(module string, backend Backend, source string) {
|
||||
defaultCatalog.RegisterFromSource(module, backend, source)
|
||||
}
|
||||
|
||||
func (inv *Catalog) RegisterFromSource(module string, backend Backend, source string) {
|
||||
if inv == nil {
|
||||
return
|
||||
}
|
||||
inv.registerInternal(Registration{
|
||||
Module: normalizeModule(module),
|
||||
Backend: backend,
|
||||
Source: strings.TrimSpace(source),
|
||||
})
|
||||
}
|
||||
|
||||
func (inv *Catalog) registerInternal(reg Registration) {
|
||||
if reg.Module == "" {
|
||||
return
|
||||
}
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
for i := range inv.entries {
|
||||
if inv.entries[i].Module == reg.Module {
|
||||
inv.entries[i] = reg
|
||||
return
|
||||
}
|
||||
}
|
||||
inv.entries = append(inv.entries, reg)
|
||||
}
|
||||
|
||||
// Snapshot 返回当前清单的副本,按 module 名排序,便于测试和 admin 报告。
|
||||
func Snapshot() []Registration {
|
||||
return defaultCatalog.Snapshot()
|
||||
}
|
||||
|
||||
func (inv *Catalog) Snapshot() []Registration {
|
||||
if inv == nil {
|
||||
return nil
|
||||
}
|
||||
inv.mu.RLock()
|
||||
out := make([]Registration, len(inv.entries))
|
||||
copy(out, inv.entries)
|
||||
inv.mu.RUnlock()
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Module < out[j].Module })
|
||||
return out
|
||||
}
|
||||
|
||||
// Verify 对清单里每条注册都跑一次 Check。所有违规聚合成 errors.Join,
|
||||
// 调用方可以用 errors.Is(err, ErrMemoryDistProfile) 判断是否有
|
||||
// 至少一个内存后端被拒。
|
||||
//
|
||||
// 调用方应在装配完成、http server / bus subscribe 启动之前调用一次。
|
||||
func Verify(profile Profile) error {
|
||||
return defaultCatalog.Verify(profile)
|
||||
}
|
||||
|
||||
func (inv *Catalog) Verify(profile Profile) error {
|
||||
snapshot := inv.Snapshot()
|
||||
var errs []error
|
||||
for _, reg := range snapshot {
|
||||
if err := Check(reg.Module, reg.Backend, profile); err != nil {
|
||||
if reg.Source != "" {
|
||||
err = wrapWithSource(err, reg.Source)
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Reset 清空清单。仅用于测试 / hot-reload。生产代码不应该调用。
|
||||
func Reset() {
|
||||
defaultCatalog.Reset()
|
||||
}
|
||||
|
||||
func (inv *Catalog) Reset() {
|
||||
if inv == nil {
|
||||
return
|
||||
}
|
||||
inv.mu.Lock()
|
||||
inv.entries = nil
|
||||
inv.mu.Unlock()
|
||||
}
|
||||
|
||||
// Has 返回 module 是否已注册。便于测试断言。
|
||||
func Has(module string) bool {
|
||||
return defaultCatalog.Has(module)
|
||||
}
|
||||
|
||||
func (inv *Catalog) Has(module string) bool {
|
||||
if inv == nil {
|
||||
return false
|
||||
}
|
||||
module = normalizeModule(module)
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
for _, r := range inv.entries {
|
||||
if r.Module == module {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeModule(name string) string {
|
||||
return strings.TrimSpace(name)
|
||||
}
|
||||
|
||||
// sourceWrappedError 让 errors.Is 仍能命中底层 sentinel,同时携带 Source。
|
||||
type sourceWrappedError struct {
|
||||
inner error
|
||||
source string
|
||||
}
|
||||
|
||||
func (e *sourceWrappedError) Error() string {
|
||||
if e.source == "" {
|
||||
return e.inner.Error()
|
||||
}
|
||||
return e.inner.Error() + " (registered at " + e.source + ")"
|
||||
}
|
||||
|
||||
func (e *sourceWrappedError) Unwrap() error { return e.inner }
|
||||
|
||||
func wrapWithSource(inner error, source string) error {
|
||||
source = strings.TrimSpace(source)
|
||||
if inner == nil || source == "" {
|
||||
return inner
|
||||
}
|
||||
return &sourceWrappedError{inner: inner, source: source}
|
||||
}
|
||||
Reference in new issue
Block a user