34 KiB
Raw Permalink Blame History

Tasks: Port ROK Hero & Bag System

交付策略P1-P3 服务端业务先行,用 GM 命令 + 日志验证不写 UI。所有 YIUI 面板集中在 P4 一次性补齐。每个新 RPC Handler MUST 同时新增对应 GM 命令。

复用原则(详见 Doc/ET-Packages-Audit.md38 个 ET 包已覆盖 GM / 日志 / BSON / 协议 / 资源热更 / UI / 网络。本变更严格遵守"能复用不重写":日志扩展 Log 类、GM 直接用 [GM]/[ConsoleHandler] 属性、BSON 复用 MongoHelper、登录扩展 C2G_LoginGateHandler。仅以下能力新建业务包:db / logging / config / hero / bag / gacha

1. P0 - 基础设施准备

1.1 客户端编译产物

  • 1.1.1 在 Unity 中执行 ET/Loader/Compile (F6) 生成客户端热更 DLL
  • 1.1.2 检查 Packages/cn.etetet.loader/Bundles/Code/ 下存在 ET.Model.dll.bytes / ET.Hotfix.dll.bytes / ET.ModelView.dll.bytes / ET.HotfixView.dll.bytes
  • 1.1.3 执行 ET/HybridCLR/Init 初始化 AOT 元数据(如未执行过)

1.2 服务端编译产物

  • 1.2.1 执行 dotnet build "My project/ET.sln" -c Debug 编译服务端
  • 1.2.2 检查 My project/Bin/ET.App.dll 存在
  • 1.2.3 编写 tools/build-server.ps1 一键编译脚本

1.3 跑通 Login Demo

  • 1.3.1 在 Unity Build Settings 中添加 Packages/cn.etetet.loader/Scenes/Init.unity
  • 1.3.2 启动 ET Server (单进程模式)ET/Loader/ServerTools → "Start Server"
  • 1.3.3 启动 Unity Play验证 C2R_LoginR2C_LoginC2G_LoginGateG2C_LoginGate 链路打通
  • 1.3.4 验证 Gate Scene 创建了 Player Entity 并关联 Session

1.4 DBComponent 业务封装(基于现成 MongoHelper + MongoDB.Driver

  • 1.4.1 复用确认cn.etetet.core/MongoHelper 已有 BSON 序列化、com.etetet.init/Plugins/MongoDB 已有原生 DLL仅缺业务层封装
  • 1.4.2 新建 Packages/cn.etetet.db/ 包目录与 package.json依赖 cn.etetet.core
  • 1.4.3 定义 IDBComponent 接口Save/Query/QueryByEntityId/Update/Delete
  • 1.4.4 实现 MongoDBComponent:用 IMongoClient 直连 + MongoHelper.ToBson/FromBson 序列化Collection 命名约定 = typeof(T).Name
  • 1.4.5 实现 MemoryDBComponentDictionary<long, byte[]> + MongoHelper.Clone)开发期默认
  • 1.4.6 在 Gate Scene 启动时根据 StartConfig.DBConnection 二选一挂载
  • 1.4.7 实现 DBSaveComponent(脏标记 + 30 秒定时批量 Save + Player 下线强制 Flush
  • 1.4.8 添加 Entity.SetDirty() 扩展方法(写入 dirty set
  • 1.4.9 单元测试save → query → update → delete 闭环(内存模式 + Mongo 模式各一遍)

1.5 Player 数据加载(扩展现有 cn.etetet.login,不重写

  • 1.5.1 复用确认C2G_LoginGateHandler 已有,目前只创建内存 Player扩展点 = 在 await CreatePlayer(...) 后插入 DB 加载分支
  • 1.5.2 在 cn.etetet.login/Hotfix/Server/Gate/C2G_LoginGateHandler.cs 加入 await DBHelper.LoadOrCreatePlayer(player, dbComponent) 调用(约 5-10 行)
  • 1.5.3 新建 Packages/cn.etetet.config/Datas/InitialResource.xlsx(首次登录发放 RewardEntry 列表)
  • 1.5.4 实现 DBHelper.LoadOrCreatePlayerDB 查 → 有则反序列化挂 Hero/Bag/Gacha Component无则新建并按 InitialResource 配置调 BagComponentSystem.AddItems
  • 1.5.5 注册 Session Dispose 事件(用 ET 现有 EventSystem玩家离线时 DBSaveComponent.FlushNow(playerId)
  • 1.5.6 验证:登录 → DB 中存在 → 重启服务端 → 二次登录数据完整恢复

1.6 协议工具链

  • 1.6.1 验证 ET/Proto/Proto2CS 工具可正常运行
  • 1.6.2 在 Packages/cn.etetet.proto/Proto/ 新建占位 HeroOuter_C_3000.proto(仅含 1 个 ping 消息测试)
  • 1.6.3 运行 Proto2CS 验证代码生成到 CodeMode/Model/{Client|Server|ClientServer}/HeroOuter_C_3000.cs
  • 1.6.4 添加新消息后客户端能正常发送、服务端 Handler 能接收
  • 1.6.5 给所有 C2G/C2R 消息基类加入 reqId 字段uuid 字符串,便于日志全链路追踪)

1.7 Luban 配置工具链(替代 ExcelExporter

  • 1.7.1 下载 luban 工具dotnet luban放到 tools/luban/
  • 1.7.2 新建 Config/Defines/Root.bean.xml + Config/Datas/ 目录结构
  • 1.7.3 编写 tools/luban/gen.ps1 一键导出脚本(生成 C# 到 Packages/cn.etetet.config/Scripts/Model/Share/cfg/json 到 Bundles/Config/
  • 1.7.4 写一张示例表 ItemConfig.xlsx + Item.bean.xml(含 itemId / itemType / name / icon / useType / maxStack / quality / desc / 通用 RewardEntry 字段示例)
  • 1.7.5 跑通 gen.ps1 → 生成代码 + json 文件
  • 1.7.6 在 YooAsset AssetBundleCollectorSettingBundles/Config/ 收集规则
  • 1.7.7 新建 Packages/cn.etetet.config/ 包,实现 ConfigComponent 挂在 Scene 上加载 Luban Tables
  • 1.7.8 实现 SceneExtensions.Cfg() 扩展方法
  • 1.7.9 服务端 + 客户端 都能调 scene.Cfg().TbItem.Get(1) 查到金币配置
  • 1.7.10 编写 Luban 使用文档 Doc/Luban-Guide.md

1.8 日志扩展(基于现成 Log + NLog仅加扩展方法

  • 1.8.1 复用确认cn.etetet.core/Log.cs 已有 Log.Debug/Info/Warning/Error/Tracecn.etetet.loader 已集成 NLog 后端;不重写不替换
  • 1.8.2 新建 Packages/cn.etetet.logging/ 包(依赖 cn.etetet.core + cn.etetet.loader
  • 1.8.3 在新包内加扩展方法:Log.Audit(module, action, payload) / Log.Slow(module, action, durationMs, ctx) / Log.Module(tag, level, msg)全部走 ET 现有 Log 入口,仅做格式化前缀
  • 1.8.4 提供 NLog.config.template3 个 target 分别输出 Logs/Business/ / Logs/Audit/{module}/ / Logs/Error/jsonl 行格式)+ rolling by day
  • 1.8.5 提供 LoggingInstaller:服务端启动时合并 template 到现有 Packages/cn.etetet.loader/Scripts/Loader/Server/NLog.config
  • 1.8.6 实现 Log.Audit 同步落盘NLog <target type="AsyncWrapper"> 关闭 + KeepFileOpen="true" 强制 flush
  • 1.8.7 约定模块标签前缀:[Hero] [Bag] [Gacha] [Equip] [DB] [Login] [GM] [Net]仅写文档约定,不做编译期校验
  • 1.8.8 实现 RPC Handler 基类 RpcMessageHandler<TReq,TResp>:在 OnRun 前后自动 Log.Info($"[{module}] {action} in: ..." ) + 耗时计算(业务 Handler 改继承即可,老 Handler 不强制
  • 1.8.9 实现 reqId 透传:在 C2G/C2R proto 基类加 reqId 字段(任务 1.6.5 已包含+ Handler 基类把 reqId 放到 AsyncLocal<string>,扩展 Log.Info 自动拼前缀
  • 1.8.10 实现慢操作告警:包装 IDBComponent 入口 + MessageDispatcher超阈值DB 查询 100ms / 写入 200ms / Handler 500msLog.Slow
  • 1.8.11 实现错误码降频:同 playerId 同 errorCode 1 分钟内仅首条 + 计数(用 MemoryCache 1 分钟 TTL
  • 1.8.12 用 1.9 注册 gm SetLogLevel <module> <level> 运行时调级(写 NLogManager.Configuration.LoggingRules
  • 1.8.13 实现敏感字段脱敏(配置化 MaskFieldsproto attribute 标记或 Json 路径配置)
  • 1.8.14 实现客户端日志桥接 C2G_ClientLogReport + 服务端 Logs/Client/{date}.log默认开发期开启Release 关闭)
  • 1.8.15 日志单测:扩展方法格式正确 / 模块过滤 / 同步落盘验证

1.9 GM 业务命令接入(完全复用 cn.etetet.yiuigm + cn.etetet.console,零框架开发

  • 1.9.1 复用确认
    • cn.etetet.yiuigm:已有 [GM] 属性 + IGMCommand 接口 + 完整 GMPanel UI参数 6 类型)
    • cn.etetet.console:已有 [ConsoleHandler] 属性 + IConsoleHandler 接口 + stdin REPL
    • 不新建 GM 框架,仅按这两个属性写业务 GM 类
  • 1.9.2 新建文档 Doc/GM-Commands.md:约定服务端 console 用 cn.etetet.console、客户端 panel 用 cn.etetet.yiuigm、共享业务实现走 GMHelper 静态类
  • 1.9.3 新建 cn.etetet.gachainvoke / cn.etetet.heroinvoke / cn.etetet.baginvoke 跨包调用契约(用 cn.etetet.yiuiinvoke),让 GM 类不需要直接依赖业务包
  • 1.9.4 在 cn.etetet.logging 提供 Log.AuditGM(operator, action, targetPlayer, args, success) 专用方法,所有 GM 业务类调用前自动审计
  • 1.9.5 用 #if ENABLE_GM 包裹所有 [GM] 业务类(参考 cn.etetet.yiuigm/Scripts/HotfixView/Client/GM/GM_Command_Test.cs 已有的条件编译模式)
  • 1.9.6 编写 CI 校验脚本 tools/ci/check-release-no-gm.ps1Release IL 不含 EGMType 字符串
  • 1.9.7 编写 CI 校验脚本 tools/ci/check-rpc-has-gm.ps1:扫描每个 *Handler.cs,确认对应 GM_*.cs[ConsoleHandler] 类存在

1.10 错误码体系

  • 1.10.1 新建 HeroBagErrorCode.cs 集中定义所有 Hero/Bag/Equipment/Gacha 错误码数值段位约定3xxxx Hero、4xxxx Bag、5xxxx Equip、6xxxx Gacha
  • 1.10.2 客户端添加 ErrorMessageHelper.GetMessage(int errorCode) 本地化文案;展示走 cn.etetet.yiuitipsTipsHelper.OpenSync<TipsTextViewComponent>(scene, ErrorMessageHelper.GetMessage(code))禁止自写 Toast
  • 1.10.3 服务端 Handler 返回错误码时自动写 L3 错误日志

1.11 通用 Item 基础设施(整个系统的基础

  • 1.11.1 在 Luban Common.bean.xml 定义 RewardEntry { itemId, count } / CostEntry { itemId, count }
  • 1.11.2 在 proto Common_3000.proto 定义同名 message与 Luban 一致)
  • 1.11.3 在 BagComponentSystem 上预留 AddItems(rewards) / RemoveItems(costs) / GetItemCount(itemId) / HasItems(costs) 接口签名
  • 1.11.4 在 Luban ItemConfig 中提前定义货币 itemId1=金币、2=钻石、3=PvP币写入 Datas/ItemConfig.xlsx 首批条目)
  • 1.11.5 写设计文档约定:全游戏所有可获得资源必须用 itemId 表示,禁止在 Player Entity 上加货币字段

1.12 GM Web Console策划/产品可见)— 基于 console + http 包,最小化新增

  • 1.12.1 复用确认:服务端 stdin REPL 已现成(cn.etetet.consolecn.etetet.http 已现成轻量 HTTP Server
  • 1.12.2 新建 tools/gm-web/index.htmlinput + fetch + 命令历史)作为静态资源
  • 1.12.3 实现 [HttpHandler("/gm")] HttpGMHandler:转发 POST body 到 ConsoleComponentSystem 命令分发器(直接复用现有 console 命令注册
  • 1.12.4 简单密码校验StartConfig 配 GMWebPassword
  • 1.12.5 默认仅 Debug 包加载Release 不监听端口

1.13 ROK 配置迁移工具链(新任务:取代手工录入

  • 1.13.1 背景沉淀:在 Doc/ROK-Config-Source.md 记录数据源真相ROK 服务端只有 Configs.dataLua 表tabtoy 生成ROK 客户端有加密 SQLite + 205 个 *Define.cs DTO无 Excel 源文件
  • 1.13.2 拉取姐妹工程 E:/Game/gmd/Tools/export_hero_json_from_configs_data.py 作为基础(已验证可解析 Hero/HeroLevel/HeroStar
  • 1.13.3 在 tools/rok-migrate/export.py 扩展支持本次需要的表Hero/HeroLevel/HeroStar/HeroSkill/HeroTalent/Item/Equip/GachaPool/GachaWeight 等)
  • 1.13.4 在 tools/rok-migrate/define_to_bean.py 写 Roslyn-free 文本扫描器:读 ROK/Client/.../*Define.cs → 提取字段名 + 类型 → 输出 Luban *.bean.xml
  • 1.13.5 在 tools/rok-migrate/json_to_luban_xlsx.py 写 JSON → Luban *.xlsx 转换器(首行类型 / 二行字段名 / 三行注释 / 数据行)
  • 1.13.6 一键脚本 tools/rok-migrate/run.ps1bean.xml + xlsx 一次出齐到 Config/Defines/Config/Datas/
  • 1.13.7 跑通 Hero + Item 两张表端到端:run.ps1tools/luban/gen.ps1 → 服务端 scene.Cfg().TbHero.Get(10001) 拿到 ROK 真实数据
  • 1.13.8 文档化:每张表的 ROK 源字段 ↔ Luban bean 字段映射表

2. P1 - 抽卡 + 通用 Item + 英雄基础(纯服务端 + GM 验证,零 UI

执行顺序调整2026-05-27 用户确认):先跳过 2.0 抽卡,优先做 2.1+ 英雄/背包抽卡并入“酒馆Tavern发奖”语义后再回填 2.0。

2.0 抽卡系统(已延期:并入酒馆 Tavern 语义后再落地

2.0.1 抽卡 Luban 配置

  • 2.0.1.1 在 Config/Defines/Gacha.bean.xml 定义 GachaPool beanid / name / singleCost / multiCost / guaranteeCount / guaranteeRare / multiGuaranteeRare / openTime / closeTime
  • 2.0.1.2 定义 GachaWeightItem beanpoolId / entryIndex / reward: RewardEntry / weight / rare
  • 2.0.1.3 创建 Config/Datas/GachaPool.xlsx + GachaWeight.xlsx填充测试卡池1 个普池(消耗金币)+ 1 个高级池(消耗钻石)
  • 2.0.1.4 跑 gen.ps1 验证代码生成 + json 输出

2.0.2 抽卡协议

  • 2.0.2.1 创建 Packages/cn.etetet.gacha/Proto/GachaOuter_C_3200.proto
  • 2.0.2.2 定义 DTOGachaPoolStats { poolId, totalCount, guaranteeRemain }
  • 2.0.2.3 定义 RPCC2G_GachaDraw { poolId, drawType } / R2C_GachaDraw { error, rewards: repeated RewardEntry, isNewHero: repeated bool }
  • 2.0.2.4 定义推送:Gacha_GachaInfo { stats: repeated GachaPoolStats }
  • 2.0.2.5 运行 Proto2CS 生成代码

2.0.3 服务端 GachaComponent

  • 2.0.3.1 创建 cn.etetet.gacha/Scripts/Model/Server/GachaComponent(挂在 Player 下)
  • 2.0.3.2 含 Dictionary<int, GachaPoolStats> PoolStats,标记 [MongoElement] 持久化
  • 2.0.3.3 实现 GachaComponentSystem.Draw(poolId, drawType) 完整算法(消耗→随机→保底→发奖→更新统计→审计日志)
  • 2.0.3.4 实现 UpdateGuarantee(poolId, rare) 保底计数更新
  • 2.0.3.5 实现 ReplaceWithGuaranteed(rewards, weights, minRare) 强制保底
  • 2.0.3.6 实现 RandomNumberGenerator 工具类CSPRNG System.Security.Cryptography.RandomNumberGenerator禁止使用 UnityEngine.Random
  • 2.0.3.7 抽卡过程中严格走 BagComponentSystem.RemoveItems 扣消耗 + AddItems 发奖励,禁止绕过

2.0.4 服务端 Handler + 日志

  • 2.0.4.1 C2G_GachaDrawHandler:继承自带埋点的 RpcMessageHandler 基类(入口/出口自动 L1 日志)
  • 2.0.4.2 抽卡完成后写 L2 审计日志playerId / poolId / drawType / cost / rewards / guaranteeRemain / reqId
  • 2.0.4.3 推送 R2C_GachaDraw + Gacha_GachaInfo
  • 2.0.4.4 接口限速:单玩家 100ms 最多 1 次请求,超过返回 ERR_RATE_LIMIT 并写 L3
  • 2.0.4.5 服务端单元测试:模拟 10000 次抽卡,验证概率分布与配置误差 < 1%
  • 2.0.4.6 服务端单元测试:连续 90 抽 1 次不出保底品质 → 第 90 次必出
  • 2.0.4.7 服务端单元测试:自然抽到保底品质 → 计数重置

2.0.5 客户端 GachaComponent

  • 2.0.5.1 创建 cn.etetet.gacha/Scripts/Model/Client/GachaComponent
  • 2.0.5.2 实现 Gacha_GachaInfoHandler 同步保底进度
  • 2.0.5.3 发布 GachaUpdatedEvent

2.0.6 抽卡 GM 命令(P1 唯一验证手段

  • 2.0.6.1 gm Gacha <poolId> <times> 触发抽卡(默认 1 次,可指定 N 次)
  • 2.0.6.2 gm SimulateGacha <poolId> <times> 服务端模拟 N 次抽卡(不修改真实玩家数据),输出概率统计
  • 2.0.6.3 gm ShowGachaStats [playerId] 输出玩家所有卡池的累计/保底进度
  • 2.0.6.4 gm ResetGachaGuarantee <poolId> [playerId] 重置保底计数
  • 2.0.6.5 gm SetGachaCount <poolId> <count> [playerId] 直接设置抽数(测试用)

2.0.7 抽卡验收GM + 日志)

  • 2.0.7.1 gm AddItem 2 100gm Gacha 1 1gm ShowBag → 钻石 -1奖励入包
  • 2.0.7.2 gm Gacha 1 10 → 收到 10 个奖励,钻石按十连消耗扣除
  • 2.0.7.3 gm SetGachaCount 1 89gm Gacha 1 1 必出保底品质(查审计日志确认)
  • 2.0.7.4 自然抽到保底品质 → gm ShowGachaStats 显示保底计数重置为 0
  • 2.0.7.5 不同卡池保底独立:抽卡池 A 不影响卡池 B 进度
  • 2.0.7.6 gm SimulateGacha 1 10000 概率与配置误差 < 1%
  • 2.0.7.7 gm Snapshot beforegm Gacha 1 10 → 重启 → gm DumpPlayer 对比 → 数据完整恢复
  • 2.0.7.8 篡改 reqId / 客户端伪造字段 → 日志记录但服务端结果不受影响
  • 2.0.7.9 端到端验证gm Gacha 1 100 → 抽到英雄碎片 → gm SummonHero → 招募成功

2.1 英雄 Luban 配置

  • 2.1.1 在 Config/Defines/Hero.bean.xml 定义 Hero bean含 List skillIds、List talentIds、fragmentItemId、recruitLimit 等)
  • 2.1.2 定义 HeroLevel / HeroSkill / HeroSkillEffect / HeroSkillLevel bean
  • 2.1.3 在 bean 中使用 List<RewardEntry> / List<CostEntry> 表达消耗与奖励
  • 2.1.4 填充测试数据:每张表 5 个条目
  • 2.1.5 跑 gen.ps1 验证客户端/服务端读到 scene.Cfg().TbHero.Get(heroId)

2.2 英雄协议

  • 2.2.1 创建 Packages/cn.etetet.hero/Proto/HeroOuter_C_3000.proto
  • 2.2.2 定义 DTOHeroInfoSkillInfo
  • 2.2.3 定义 RPCC2G_SummonHero / C2G_HeroAddExp / C2G_HeroSkillUp 及响应
  • 2.2.4 定义推送:Hero_HeroInfoHero_HeroInfoList
  • 2.2.5 运行 Proto2CS

2.3 服务端 Hero 实体与持久化

  • 2.3.1 创建 Hero Entity含 level/exp/star/starExp/skills/talents
  • 2.3.2 创建 HeroComponent(挂在 Player 下)
  • 2.3.3 实现 Awake/Destroy + AddHero / RemoveHero / GetHero / GetAllHeroes
  • 2.3.4 给所有持久化字段加 [MongoElement]
  • 2.3.5 验证:登录后服务端能加载玩家所有 Hero 实体

2.4 服务端业务 Handler + 日志

  • 2.4.1 C2G_SummonHeroHandler:校验碎片(BagComponentSystem.HasItems+ 不重复 + 上限 → AddHero + BagComponentSystem.RemoveItems 扣碎片
  • 2.4.2 C2G_HeroAddExpHandler:校验道具 → 累加经验 → 触发升级 → 受星级上限约束
  • 2.4.3 C2G_HeroSkillUpHandler:校验道具 → 技能等级 +1 → 受 maxLevel 约束
  • 2.4.4 HeroNoticeHelper.NotifyHeroUpdate(player, hero) 封装推送
  • 2.4.5 登录后自动推送 Hero_HeroInfoList
  • 2.4.6 所有 Handler 走 RpcMessageHandler 基类自动埋点
  • 2.4.7 关键操作(招募、首次满级、首次满技能)写 L2 审计

2.5 客户端 Hero 缓存

  • 2.5.1 创建客户端 HeroComponent 缓存
  • 2.5.2 实现 Hero_HeroInfoHandler 增量更新 / Hero_HeroInfoListHandler 全量同步
  • 2.5.3 发布 HeroUpdatedEvent / HeroListUpdatedEvent

2.6 战力计算(客户端 + 服务端共享)

  • 2.6.1 在 cn.etetet.hero/Scripts/Model/Share/HeroPowerCalculator.cs 实现 GetBaseScore / GetLevelScore / GetSkillScore / GetPower
  • 2.6.2 单元测试:相同数据 → 客户端与服务端算出相同战力

2.7 英雄 GM 命令(P1 唯一验证手段

  • 2.7.1 gm AddHero <heroId> 直接创建英雄
  • 2.7.2 gm SetHeroLevel <heroId> <level> 设置等级
  • 2.7.3 gm SetHeroSkill <heroId> <skillIndex> <level> 设置技能等级
  • 2.7.4 gm ShowHeroes [playerId] 输出 jsonl
  • 2.7.5 gm SummonHero <heroId> 走完整招募流程(验证碎片消耗逻辑)
  • 2.7.6 gm HeroAddExp <heroId> <itemId> <count> 走完整升级流程

2.8 P1 验收GM + 日志,零 UI

  • 2.8.1 gm AddItem <碎片itemId> 30gm SummonHero <heroId> → 招募成功 + 碎片扣 30
  • 2.8.2 gm HeroAddExp <heroId> <expBookId> 100 → 等级提升 + 经验书减少
  • 2.8.3 升级技能 → 技能等级 +1 → 战力变化(用 gm ShowHeroes 对比)
  • 2.8.4 重启客户端 + 服务端 → gm DumpPlayer 输出对比 → 数据完整恢复
  • 2.8.5 审计日志:tail Logs/Audit/Hero/2026-*.jsonl 含完整招募/升级记录
  • 2.8.6 错误日志:尝试用 0 碎片招募 → L3 日志含错误码 + reqId

3. P2 - 背包与装备穿戴(服务端 + GM 验证,零 UI

3.1 背包/装备 Luban 配置

  • 3.1.1 完善 Config/Defines/Item.bean.xml(含 BagItemType 枚举、useType 枚举)
  • 3.1.2 定义 Equip bean含 List 属性、equipItemType 枚举)
  • 3.1.3 填充约 30 个测试道具 + 10 个测试装备到 xlsx
  • 3.1.4 跑 gen.ps1 验证

3.2 背包/装备协议

  • 3.2.1 创建 Packages/cn.etetet.bag/Proto/BagOuter_C_3100.proto
  • 3.2.2 定义 DTOItemInfoEquipSlot
  • 3.2.3 定义 RPCC2G_UseItem / C2G_HeroWearEquip / C2G_TakeOffEquip
  • 3.2.4 定义推送:Item_ItemInfoItem_ItemInfoListItem_RemoveItem
  • 3.2.5 运行 Proto2CS

3.3 服务端 BagComponent通用 Item 入口

  • 3.3.1 创建 cn.etetet.bag/Scripts/Model/Server/BagComponent
  • 3.3.2 创建 ItemUnit 子 Entity含 itemIndex / itemId / count / heroId
  • 3.3.3 实现 AddItem(itemId, count) - 自动堆叠(非装备类)/ 新建条目(装备类)
  • 3.3.4 实现 RemoveItem(itemId, count)RemoveItemByIndex(itemIndex)
  • 3.3.5 实现 SetHeroIdOnEquip(itemIndex, heroId)
  • 3.3.6 实现 AddItems(List<RewardEntry>) / RemoveItems(List<CostEntry>) / HasItems(List<CostEntry>) 批量接口(事务性:任一失败全失败)
  • 3.3.7 实现 GetItemCount(itemId) 通用查询
  • 3.3.8 所有变更通过 BagNoticeHelper.NotifyItemChange 推送
  • 3.3.9 资源大额变动(>1000写 L2 审计日志
  • 3.3.10 玩家首次登录通过 InitialResourceConfigAddItems 发初始金币/钻石(走通用接口

3.4 服务端业务 Handler

  • 3.4.1 C2G_UseItemHandler:通用 dispatcher按 ItemConfig.useType 分发
  • 3.4.2 C2G_HeroWearEquipHandler:校验装备 + 卸旧穿新 + 同步 Hero + Item
  • 3.4.3 C2G_TakeOffEquipHandler:清空槽位 + 装备 heroId = 0
  • 3.4.4 登录后推送 Item_ItemInfoList

3.5 客户端 BagComponent

  • 3.5.1 创建客户端 BagComponent(按 6 大类分类缓存)
  • 3.5.2 实现 Item_ItemInfoHandler / Item_ItemInfoListHandler
  • 3.5.3 实现新道具标记本地存储OctoberStudio SaveManager 集成)
  • 3.5.4 发布 ItemChangedEvent / BagUpdatedEvent

3.6 背包/装备 GM 命令

  • 3.6.1 gm AddItem <itemId> <count> 通用发放(已在 P0/P1 准备)
  • 3.6.2 gm RemoveItem <itemId> <count> 通用扣除
  • 3.6.3 gm ShowBag [playerId] 输出 jsonl
  • 3.6.4 gm BagClear [playerId] 清空背包(保留绑定装备)
  • 3.6.5 gm HeroWearEquip <heroId> <itemIndex> 穿戴
  • 3.6.6 gm HeroTakeOffEquip <heroId> <slotIndex> 卸下
  • 3.6.7 gm UseItem <itemIndex> [count] 使用道具

3.7 P2 验收GM + 日志)

  • 3.7.1 gm AddItem <装备itemId> 1gm ShowBag 出现 → 查属性
  • 3.7.2 gm HeroWearEquipgm ShowHeroes 看战力变化
  • 3.7.3 gm HeroTakeOffEquip → 装备回背包未绑定状态
  • 3.7.4 切换英雄穿同一装备 → 自动从原英雄卸下
  • 3.7.5 重启验证装备穿戴关系持久化
  • 3.7.6 通用 Item 测试:金币、钻石、碎片、装备、材料都用 gm AddItem 发放,全部走 BagComponent.AddItems

4. P3 - 高级养成(服务端 + GM 验证,零 UI

4.1 升星系统

  • 4.1.1 Luban 配置 HeroStar / HeroStarExp bean
  • 4.1.2 proto C2G_HeroStarUp / R2C_HeroStarUp
  • 4.1.3 服务端 Handler扣升星材料 + 幸运计算 + 星级提升
  • 4.1.4 GM 命令:gm HeroStarUp <heroId> 走完整流程,gm SetHeroStar <heroId> <star> 直接设置
  • 4.1.5 单元测试:连续升星,验证幸运系数概率分布

4.2 觉醒系统

  • 4.2.1 给 Hero Entity 增加 IsAwakening 字段
  • 4.2.2 proto C2G_HeroAwake
  • 4.2.3 服务端 Handler校验前 4 技能满级 + 扣觉醒材料 + 解锁第 5 技能
  • 4.2.4 GM 命令:gm HeroAwake <heroId> / gm ForceAwake <heroId>(跳过校验,测试用)

4.3 天赋系统

  • 4.3.1 Luban 配置 HeroTalent / HeroTalentTree / HeroTalentMastery bean
  • 4.3.2 给 Hero Entity 增加 TalentIndex + 3 套 TalentTreeData
  • 4.3.3 proto C2G_TalentUp / C2G_ChangeTalentIndex / C2G_ResetTalent
  • 4.3.4 服务端 Handler扣点数 + 节点等级 +1 + 前置依赖校验
  • 4.3.5 GM 命令:gm TalentUp <heroId> <nodeId> / gm TalentReset <heroId> / gm ChangeTalentIndex <heroId> <index>

4.4 装备锻造

  • 4.4.1 Luban 配置扩展:Equip bean 增加 List<CostEntry> requireMaterials / List<RewardEntry> resolveRewards
  • 4.4.2 proto C2G_MakeEquipment / C2G_ResolveEquipment / C2G_MixMaterial
  • 4.4.3 服务端 Handler扣材料 + 创建装备 / 分解装备 + 返还
  • 4.4.4 GM 命令:gm MakeEquip <equipId> / gm ResolveEquip <itemIndex> / gm MixMaterial <materialId>

4.5 P3 验收

  • 4.5.1 gm HeroStarUp 重复使英雄升至 5 星
  • 4.5.2 前 4 技能升满 → gm HeroAwake 成功 → 第 5 技能可用
  • 4.5.3 gm TalentUp 学习天赋 → gm ChangeTalentIndex 切换天赋页 → gm TalentReset 重置
  • 4.5.4 gm MakeEquip 用图纸 + 材料锻造装备 → 装备出现在背包
  • 4.5.5 gm ResolveEquip 分解装备 → 材料返还
  • 4.5.6 全流程审计日志完整:每个操作都有对应 jsonl 记录

5. P4 - UI 集中实现(深度复用 YIUI 全套子包,零造轮

前提P0-P3 服务端业务已稳定,所有协议、错误码、推送已定型。

强制约束(详见 Doc/ET-Packages-Audit.md 中 "YIUI 框架" 一节):

  • 面板打开/关闭只用 scene.YIUIRoot().OpenPanelAsync<T>()禁止自建 PanelHelper
  • 长列表只用 YIUILoopScrollChild.SetDataRefresh禁止自写 ScrollRect 池化
  • 按钮事件只用 UIEventBind* + [YIUIInvoke]禁止 Button.onClick.AddListener
  • 数据 → UI 刷新只用 u_Data*.SetValue + UIDataBind*禁止手写 Refresh 样板
  • 弹窗只用 TipsHelper.OpenWait/OpenSync禁止自写 ConfirmDialog/Toast
  • 红点只在叶子节点 SetCount,父节点 DAG 自动汇总

5.1 YIUI 通用组件准备

  • 5.1.1 复用现有 CommonHeader + YIUICloseCommon(不再造顶栏/关闭)
  • 5.1.2 创建 BagItemUICommon.prefab:图标用 UIDataBindImage + u_DataIcon、数量用 UIDataBindText + u_DataCount、品质边框用 UIDataBindSprite + u_DataQualityFrame、新道具标记用 UIDataBindActive + u_DataNew、不可用用 UIDataBindGray + u_DataEnabledcn.etetet.yiuieffect
  • 5.1.3 BagItemUICommonComponentSystem.Show(itemConfigId, count, options) 入口:内部全走 u_Data*.SetValue禁止手写 Image.sprite = xxx
  • 5.1.4 多尺寸预设:BagItemUICommon_Small/Mid/Large.prefab 三个变体(仅 RectTransform 不同),共享同一 Component 代码
  • 5.1.5 点击事件统一发布 BagItemClickedEventET EventSystem让宿主 Panel 处理

5.2 抽卡 UI

  • 5.2.1 用 YIUI 自动化工具生成 GachaPanel Component + System
  • 5.2.2 Prefab 用 UIEventBindClick + [YIUIInvoke] 接线(单抽/十连/切池按钮);按钮可用性用 UIDataBindGray + u_DataCanDraw(钻石不足自动灰显)
  • 5.2.3 保底进度用 UIDataBindSlider + u_DataGuaranteeProgress + UIDataBindText + u_DataGuaranteeText
  • 5.2.4 卡池列表用 cn.etetet.yiuiloopscrollrectasyncYIUILoopScrollChild.SetDataRefresh(poolList)
  • 5.2.5 RPC 期间挡点击:scene.YIUIMgr().BanLayerOptionForever() 开 → 收响应 close 关
  • 5.2.6 抽卡 CD 用 CountDownMgrcn.etetet.yiuiframework禁止自建 Timer
  • 5.2.7 GachaResultPanel10 个奖励槽用 BagItemUICommon 渲染;新英雄高亮用 UIEffect 发光 + UIParticle 粒子(cn.etetet.yiuieffect
  • 5.2.8 "确认消耗"用 TipsHelper.OpenWait<TipsMessageViewComponent>(scene, "消耗 X 钻石?") 模态等待
  • 5.2.9 抽卡完成飘字用 TipsHelper.OpenSync<TipsTextViewComponent>(scene, "招募成功")

5.3 英雄 UI

  • 5.3.1 HeroListPanel 长列表用 YIUILoopScrollChild.SetDataRefresh(heroList) + ReRenderer 增量刷新
  • 5.3.2 三段分组(已拥有 / 可召唤 / 未集齐):在数据层 IList 中插入 Header Item不改 Loop 框架
  • 5.3.3 HeroListItem 字段(等级/星级/战力/碎片数)全用 u_Data* + UIDataBind*
  • 5.3.4 SortType 切换:UIEventBindToggleGroup + 重排 IList → Loop.ReRenderer()
  • 5.3.5 HeroDetailPanel3D 模型用 YIUI3DDisplayChild.ShowAsync("HeroModel_10001")cn.etetet.yiui3ddisplay禁止自建 RT/Camera
  • 5.3.6 升级/升星/技能/觉醒 按钮 → UIEventBindClick + [YIUIInvoke] → RPC
  • 5.3.7 召唤按钮带"消耗 N 碎片"模态:TipsHelper.OpenWait

5.4 背包 UI

  • 5.4.1 BagPanel 6 Tab + YIUILoopScrollChild(每 Tab 复用同一 Loop切 Tab 仅换 IList
  • 5.4.2 道具 Tooltip自建 BagItemTooltipViewtips 包无现成 Tooltip View),容器走 TipsHelper.Open<BagItemTooltipViewComponent>(scene, itemId) 承载
  • 5.4.3 Tab 切换:UIEventBindToggleGroup 自动响应 + Loop.SetDataRefresh(filteredList)
  • 5.4.4 点击 Item → 发布 BagItemClickedEvent → Panel 弹 Tooltip → "使用 / 穿戴"按钮走 RPC
  • 5.4.5 操作确认用 TipsHelper.OpenWait;操作结果飘字用 TipsHelper.OpenSync

5.5 装备穿戴 UI

  • 5.5.1 HeroDetailPanel 8 装备槽:每个槽 = BagItemUICommon + UIEventBindClick
  • 5.5.2 EquipSelectPanelYIUILoopScrollChild 列表 + 按 SubType 过滤 IList
  • 5.5.3 点击空槽位 → OpenPanelParamAsync<EquipSelectPanelComponent>(slotIndex, subType)
  • 5.5.4 点击已穿 → TipsHelper.OpenWait "卸下 / 替换 / 取消"三选一

5.6 天赋 UI

  • 5.6.1 HeroTalentPanel 天赋树可视化:YIUI 无图编辑器组件,需自绘(普通 RectTransform 拼节点 + LineRenderer 连线)
  • 5.6.2 节点状态用 UIDataBindActive(已点亮)+ UIDataBindGray(不可点)
  • 5.6.3 切换 / 重置交互:UIEventBindClick + [YIUIInvoke] + 二次确认走 TipsHelper.OpenWait

5.7 锻造 UI

  • 5.7.1 EquipForgePanel 3 TabUIEventBindToggleGroup
  • 5.7.2 材料 / 产物用 BagItemUICommon 渲染
  • 5.7.3 锻造按钮:足够材料时正常,不足时 UIDataBindGray 灰显
  • 5.7.4 成功后 TipsHelper.OpenSync 飘字 + UIParticle 粒子动画

5.8 红点系统(完全复用 cn.etetet.yiuireddot

  • 5.8.1 扩展 ERedDotKeyType 枚举HeroCanUpgrade / HeroCanStarUp / HeroCanAwake / TalentHasPoint / BagHasNew / BagHasUsable / GachaHasFree / EquipCanForge 等
  • 5.8.2 在 UIRedDotConfigDAG 编辑器配置父子关系HeroEntry = 4 个 Hero* 之和BagEntry = Bag* 之和MainMenu = HeroEntry + BagEntry + GachaEntry + EquipForge
  • 5.8.3 业务侧只写叶子RedDotMgr.Inst.SetCount(ERedDotKeyType.HeroCanUpgrade, count)禁止给中间节点 SetCount
  • 5.8.4 Panel / Button 上挂 RedDotBind 选 Key不写代码自动显隐
  • 5.8.5 红点检查逻辑统一封装到 RedDotChecker.RefreshAll(),业务变化时调用

5.9 UI 与 OctoberStudio 集成

  • 5.9.1 在 OctoberStudio Main Menu 添加"英雄"/"背包"/"抽卡"三个按钮(普通 Button因 OctoberStudio 不是 YIUI 体系)
  • 5.9.2 按钮 OnClick → scene.YIUIRoot().OpenPanelAsync<HeroListPanelComponent>()不写 PanelHelper
  • 5.9.3 处理"未联网"状态:按钮 OnClick 前检查 Session → 未连接时 TipsHelper.OpenSync<TipsTextViewComponent>(scene, "请先登录")

5.10 P4 验收

  • 5.10.1 全功能 UI 走查:抽卡 → 招募 → 升级 → 穿戴 → 升星 → 觉醒 → 天赋 → 锻造
  • 5.10.2 UI 操作与 GM 命令产生相同的业务结果
  • 5.10.3 性能:背包列表 1000+ 道具滑动 60 FPSYIUILoopScrollChild 池化保证)
  • 5.10.4 红点正确显示与清除(叶子 SetCount → 父节点自动汇总)
  • 5.10.5 代码扫描验证零造轮CI 脚本 tools/ci/check-no-ui-wheel.ps1 校验业务包内不含 Button.onClick.AddListener / Image.sprite = / Resources.Load / 自写 ScrollRect 池化)

6. 跨阶段任务

6.1 单元测试与集成测试

  • 6.1.1 服务端 Handler 单元测试覆盖率 ≥ 60%
  • 6.1.2 客户端 Component 缓存逻辑测试
  • 6.1.3 协议序列化往返测试
  • 6.1.4 DBComponent 持久化测试(内存模式 + MongoDB 模式各跑一遍)
  • 6.1.5 端到端测试:登录 → 抽卡 → 招募 → 升级 → 穿戴 → 退出 → 重新登录验证
  • 6.1.6 抽卡概率回归测试10000 次抽卡,各品质实际概率与配置误差 < 1%
  • 6.1.7 抽卡保底回归测试200 抽,保底触发次数与理论值匹配

6.2 文档

  • 6.2.1 Doc/Luban-Guide.md Luban 使用文档
  • 6.2.2 Doc/GM-Commands.md 所有 GM 命令列表 + 用法
  • 6.2.3 Doc/Logging-Guide.md 日志体系说明 + grep/jq 示例
  • 6.2.4 Doc/Hero-System-Guide.md 业务文档
  • 6.2.5 Doc/Bag-System-Guide.md
  • 6.2.6 Doc/Server-Setup.md(如何起服 + 起 mongo + 编译)
  • 6.2.7 在工程根 README.md 中添加新业务模块入口指引

6.3 性能与监控

  • 6.3.1 验证慢操作告警实际生效(构造 100ms+ 的 DB 操作触发 L3 日志)
  • 6.3.2 验证单服务器 100 个模拟玩家并发登录 + 业务操作不崩溃
  • 6.3.3 客户端背包列表 1000+ 道具滑动 60 FPSLoopScrollRect 确保)
  • 6.3.4 日志写盘性能测试1000 QPS 业务日志 + 100 QPS 审计日志稳定

6.4 CI 校验

  • 6.4.1 tools/ci/check-release-no-gm.ps1 Release IL 不含 GM 代码
  • 6.4.2 tools/ci/check-rpc-has-gm.ps1 每个 RPC Handler 有对应 GM 命令
  • 6.4.3 tools/ci/check-logging.ps1 每个 Handler 必含日志埋点(继承 RpcMessageHandler 或显式声明)
  • 6.4.4 tools/ci/check-no-ui-wheel.ps1 业务包 cn.etetet.hero/bag/gacha 内禁止出现 Button.onClick.AddListener / Image.sprite = / Resources.Load / 手写 ScrollRect 池化等 YIUI 已覆盖能力的造轮代码
  • 6.4.5 openspec validate --strict 在 CI 中跑

6.5 上线前检查

  • 6.5.1 所有 GM 命令仅 Debug 包启用Release 包剥离 + IL 校验通过
  • 6.5.2 错误码本地化覆盖(中文 + 备 EN
  • 6.5.3 数据备份策略MongoDB 每日自动备份脚本)
  • 6.5.4 服务端日志切割与归档L2 转 OSS
  • 6.5.5 HybridCLR 在 IL2CPP 包下验证:ET/HybridCLR/Generate/All
  • 6.5.6 概率审计:所有卡池跑 SimulateGacha 10000 次,截图保留