Files
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

206 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}
}