肉鸽词条选择、英雄归属与池次数
本页于 2026-10-15 整合现有功能稿、飞书内容及客户端 origin/develop@7f0636c9 的功能资料。原 draft/unknown 不因整理自动变为批准;设计规则、源配置与运行结果分别说明。本次只做文档及源码审阅,没有执行游戏功能测试。
入口与边界
该功能是宝箱/玩法配置触发的Buff三选一,与每波手牌5选3分别建池。候选按权重抽取,英雄归属限制有效英雄,次数耗尽移出;选择期间暂停刷怪、怪物、普攻与手动技能,完成恢复。怪物可按掉落配置触发宝箱选择,出现次数与权重由配置决定,不默认所有词条有前置链。
池、配置与实现
概念速查
| 概念 | 来源 | 说明 |
|---|---|---|
| 词条 (Buff/Perk) | TbBattleEffectSelectconfig | 即肉鸽 Buff,Id = Perk 资源 Id。分 DefaultAttr / HeroAttr / HeroLevelAttr 三种类型 |
三选一池 (_pool) | BuffSelectService | List<BuffInfo>,每个条目有 SelectNum(可被选次数)和 Weight(权重) |
| 携带宝箱 BUFF | TbHeroconfig.BattleEffectIDs | 英雄表上的 int[],定义该英雄"能刷出哪些词条"。用于构建白名单过滤池 |
| 英雄归属 | TbBattleEffectSelectconfig.OwnershipHero | 0=通用(全员施加),>0=仅该 roleId 英雄 |
| AttrType.DefaultAttr | AttrType 枚举 | 默认词条,池初始化时加载 |
| AttrType.HeroAttr | AttrType 枚举 | 英雄携带词条,池初始化时与 DefaultAttr 一起加载(按白名单过滤) |
| AttrType.HeroLevelAttr | AttrType 枚举 | 等级解锁词条,不在初始化时加载,由 GetNewAttr 注入 |
| InitialGetAttr | TbHeroLevelAttrconfig 多态字段 | 战斗开始直接获得词条 + 占用一次池子可选次数 |
| GetNewAttr | TbHeroLevelAttrconfig 多态字段 | 战斗开始将词条注入三选一池(增加一个可选项) |
| OverloadUnlock | TbHeroLevelAttrconfig 多态字段 | 超载解锁标记,不影响池,由 ComHeroSpellItem 运行时检查 |
| Mulberry32 | BuffSelectService._rng32 | 可序列化 32 位 PRNG,支持快照恢复后续抽 |
核心状态
_pool List<BuffInfo> 三选一池(Id/Weight/SelectNum/OwnershipHero)
_selectedHeroIds HashSet<int> 当前出战英雄 roleId
_battleDeployedHeroEcsUniqueIds HashSet<int> 场上英雄 ECS UniqueId(通用词条全员施加用)
_accumulatedChosenBuffIds List<int> 本局已选词条 Id(按选择顺序),写入快照
_rng32 / _rngBound uint / bool Mulberry32 状态
_preloadCts CancellationTokenSource 预加载取消令牌
两条初始化路径
路径 1:正常开局
LevelBattleInitState.RunQueueBootstrapAsync()
│
├─ 1. InitializeBattlePool(heroesWithLevel) ← 先建池
│ ├─ 汇总白名单(各英雄 BattleEffectIDs)
│ ├─ 遍历 TbBattleEffectSelectconfig:DefaultAttr / HeroAttr + 白名单过滤 → _pool
│ ├─ RaiseBuffSelectChanged([])
│ └─ StartPreloadPoolPerkConfigs() (异步)
│
├─ 2. 循环 CreateHeroEntityAsync(applyMilestonePerks: true)
│ └─ ApplyHeroMilestonePerks(uid, roleId, level):
│ ├─ InitialGetAttr → ConsumeBuffSelectNum + ApplyPerkToActor
│ └─ GetNewAttr → EnsureBuffInPool (注入池)
│
└─ 3. (不再发 EvtRebuildBuffPool)
关键时序:池在英雄创建之前建好,确保 ApplyHeroMilestonePerks 中的 ConsumeBuffSelectNum 能找到目标词条。
路径 2:当前快照恢复
当前快照v8在创建英雄时使用 DeferToSnapshot 避免重复授予开局词条。恢复保存的池子、已选词条和RNG状态,再按快照登记新英雄实体并恢复词条;不同时重跑开局里程碑和历史选择列表来重复叠加。准确调用顺序及角色/区域/手牌恢复见战斗续战。旧技术README逐条重放历史Buff的流程不能继续作为当前恢复契约。
池子生命周期
InitializeBattlePool → 清空 + 重建 (正常开局)
RebuildBattlePoolFromHeroRoleIds → 清空 + 重建 (EvtRebuildBuffPool, 战斗中追加)
RestoreFromBattleSnapshot → 清空 + 从快照恢复 (重登)
ConsumeChosenBuff → SelectNum -= 1, 到 0 移除
ConsumeBuffSelectNum → SelectNum -= 1, 到 0 移除 (不施加 Perk)
EnsureBuffInPool → 已存在则 SelectNum += count, 否则新建
Perk 施加路径
用户选择宝箱 Buff
→ ConsumeChosenBuff → ApplyChosenBuffPerk
└─ ApplyPerkToActorSafe: TryGetPerkDefinition → AddPerkToActor
未注册 → LoadPerkConfig 异步 → AddPerkToActor
开局 InitialGetAttr
→ ApplyHeroMilestonePerks → ApplyPerkToActor (CharacterFacade)
└─ TryGetPerkDefinition → AddPerkToActor
未注册 → TryLoadAndApplyPerkAsync.Forget()
重登恢复
→ 按快照v8恢复角色词条;不额外重复授予历史Buff
└─ 同上 ApplyPerkToActorSafe 路径
两条路径都有 Perk 未注册时的异步 fallback,不会静默丢失。
快照序列化
保存: BattleSnapshotCollector.Collect → CopyBuffSelectStateTo()
写入: _rng32 / _selectedHeroIds(排序) / _pool(Id+SelectNum) / _accumulatedChosenBuffIds
恢复: BattleSnapshotWorldApplier.TryApply → RestoreFromBattleSnapshot()
读取: 同上四字段
注意: BuffSelectResumeBlockPresent 为 true 时才恢复(旧快照无此块)
关键数据表
| 表 | 关键字段 | 用途 |
|---|---|---|
TbBattleEffectSelectconfig | Id, AttrType, Weight, SelectNum, OwnershipHero | 词条定义,池数据源 |
TbHeroconfig | BattleEffectIDs (int[]) | 英雄携带宝箱 BUFF,构建白名单 |
TbHeroLevelAttrconfig | RoleID, Level, HeroLevelAttrTypeData (多态) | 英雄等级里程碑 |
TbHeroLevelAttrEX | GetMilestonesUpToLevel(heroId, level) | 查询指定等级以下所有里程碑 |
当前恢复边界
技术README保存了早期初始化说明;当前战斗快照v8通过DeferToSnapshot避免英雄预注册词条重复授予,最终恢复顺序以战斗续战为准。OverloadUnlock名称属于旧词条定义,当前Lv.4偏向以进化表的OverloadReplaceSkill决定;不得把旧英雄配置字段的存在解释为能量或赌超载恢复启用。
来源
客户端Gameplay/Features/RoguelikeUpgrade/README.md、Docs/战斗读档逻辑梳理.md;飞书关卡流程的Buff池、次数、暂停要求;旧combat-skill/battle-mission。