Files
90CNServer/server/internal/services/logic/manifest.go
T
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

157 lines
5.9 KiB
Go

package logic
import (
"errors"
"fmt"
"longheng.io/server/internal/modules/samples/world"
appkit "longheng.io/server/internal/platform/app"
"longheng.io/server/internal/platform/dispatch"
"longheng.io/server/internal/platform/integrity"
modulemgr "longheng.io/server/internal/platform/module"
"longheng.io/server/internal/platform/notification"
"longheng.io/server/internal/platform/readmodel"
"longheng.io/server/internal/platform/runtimeguard"
"longheng.io/server/internal/platform/scripthost"
"longheng.io/server/internal/platform/secrets"
"longheng.io/server/internal/reference/player"
"longheng.io/server/internal/runtime/leaderboard"
"longheng.io/server/internal/runtime/moderation"
"longheng.io/server/internal/runtime/notice"
"longheng.io/server/internal/runtime/redeem"
logichandlers "longheng.io/server/internal/services/logic/handlers"
)
// ErrManifestMemStore 表示默认 manifest 在分布式环境仍使用单节点内存后端。
var ErrManifestMemStore = errors.New("logic default manifest uses single-node runtime backend")
// GameManifestProvider 根据框架环境构建游戏 manifest。
type GameManifestProvider func(*appkit.Env) (GameManifest, error)
// ExtensionPoints 是项目侧接入平台扩展包的 wiring 表面。
// 它刻意放在 GameManifest 中,让项目能挂接具体 provider,
// 同时避免 platform 包反向 import services 或游戏专属 runtime 代码。
type ExtensionPoints struct {
ScriptHost scripthost.Host
ScriptRuntime scripthost.Runtime
Secrets secrets.Provider
Push notification.PushProvider
Mail notification.MailProvider
Integrity integrity.ActionValidator
ReplayDigest integrity.ReplayDigestProvider
RiskScoreProvider integrity.RiskScoreProvider
}
// GameManifest 描述 logic/playerd 要装配的模块、运行时和 handler。
type GameManifest struct {
Modules []modulemgr.Module
Players *player.Module
World *world.Module
Accounts *AccountManager
Leaderboards leaderboard.Runtime
Sanctions moderation.SanctionStore
Notices notice.Service
Redeems redeem.Service
AdminRoutes []AdminRouteRegistrar
RegisterHandlers func(*dispatch.Mux) error
RegisterReadModels func(*readmodel.AdminRegistry)
Extensions ExtensionPoints
}
// BlankManifest 返回不装配业务模块的 GameManifest。
// 新项目应以它为起点,通过 Modules / Players / RegisterHandlers 注册自己的
// player、world 和 runtime 类型。
func BlankManifest(_ *appkit.Env) (GameManifest, error) {
return GameManifest{}, nil
}
// DemoGameManifest 装配内置示例模块用于本地演示。
// 即使本地配置为了 route metadata smoke 保持 topology 开启,示例 runtime 也按单节点处理。
func DemoGameManifest(env *appkit.Env) (GameManifest, error) {
if env == nil {
env = &appkit.Env{}
}
demoEnv := *env
demoEnv.Config.Topology.Enabled = false
return DefaultGameManifest(&demoEnv)
}
// DefaultPlayerDManifest 是 playerd 的参考 manifest。
// 它只装配示例 Player/Profile 模块,不继承仅属于 logic 的 world、leaderboard、notice 或 redeem 示例 runtime。
func DefaultPlayerDManifest(env *appkit.Env) (GameManifest, error) {
profileStore, summaryStore := newPlayerStores(env.Config.ProfileStore, env)
playerModule, err := player.NewWithStoresChecked(env.Logger, profileStore, summaryStore)
if err != nil {
return GameManifest{}, err
}
if env.EventLog != nil {
playerModule.UseEventLog(env.EventLog, env.Config.EventLog.Strict)
}
return GameManifest{
Modules: []modulemgr.Module{
playerModule,
},
Players: playerModule,
RegisterHandlers: func(mux *dispatch.Mux) error {
return logichandlers.RegisterAll(mux, logichandlers.Default(logichandlers.Context{Players: playerModule})...)
},
}, nil
}
// DefaultGameManifest 创建内置演示用 logic manifest。
func DefaultGameManifest(env *appkit.Env) (GameManifest, error) {
profileStore, summaryStore := newPlayerStores(env.Config.ProfileStore, env)
playerModule, err := player.NewWithStoresChecked(env.Logger, profileStore, summaryStore)
if err != nil {
return GameManifest{}, err
}
if env.EventLog != nil {
playerModule.UseEventLog(env.EventLog, env.Config.EventLog.Strict)
}
worldModule := world.New(env.Logger)
leaderboards := leaderboard.New(leaderboard.Options{Name: env.Config.Service.Name + "-leaderboards"})
sanctions := moderation.NewMemorySanctionStore()
profile := runtimeguard.ProfileFromEnvironment(env.Config.Service.Environment, env.Config.Topology.Enabled)
notices := notice.Service{
Store: notice.NewMemoryStore(),
EventLog: env.EventLog,
}
if env.Bus != nil {
publishers := []notice.LivePublisher{notice.NewBusPublisher(env.Bus)}
if env.OnlinePush != nil {
publishers = append(publishers, notice.NewOnlinePushPublisher(env.OnlinePush))
} else {
publishers = append(publishers, notice.NewGatewayPushPublisher(env.Bus))
}
notices.Publisher = notice.NewAsyncLivePublisher(notice.NewFanoutPublisher(publishers...))
}
redeems := redeem.Service{
Store: redeem.NewMemoryStore(),
EventLog: env.EventLog,
}
manifest := GameManifest{
Modules: []modulemgr.Module{
playerModule,
worldModule,
},
Players: playerModule,
World: worldModule,
Leaderboards: leaderboards,
Sanctions: sanctions,
Notices: notices,
Redeems: redeems,
AdminRoutes: []AdminRouteRegistrar{
NoticeAdminRouteRegistrar(notices),
RedeemAdminRouteRegistrar(redeems),
},
RegisterHandlers: func(mux *dispatch.Mux) error {
return logichandlers.RegisterAll(mux, logichandlers.Default(logichandlers.Context{Players: playerModule})...)
},
}
if profile.Distributed {
return manifest, fmt.Errorf("%w: notice and redeem default stores are memory-only; provide a project manifest with durable stores",
ErrManifestMemStore)
}
return manifest, nil
}