Files
pixelheros/docs/superpowers/specs/2026-07-05-rogue-config-refactor-design.md
panFD 251caf0b36 docs(rogue): add refactor design for rule-based spawning
Replace pure-random spawning with hard-coded wave configs, modular squad templates, and dual-layer enchant system (wave + individual).
2026-07-05 22:29:08 +08:00

546 lines
24 KiB
Markdown
Raw 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.
# RogueConfig 重构设计:从纯随机到规则化随机组合
- **创建日期**: 2026-07-05
- **状态**: 已批准(待实施)
- **作用域**: 重构 `assets/script/game/map/RogueConfig.ts` 及配套调用方
- **目标**: 把肉鸽刷怪系统从「每只怪独立随机」改造为「在硬编码规则内的随机组合变动」
---
## 1. 背景与问题
当前 [RogueConfig.ts](../../../assets/script/game/map/RogueConfig.ts) 的 `RogueSpawningEngine.generateWave()` 存在以下问题:
1. **纯随机生成**:每只怪独立从 6 种普通类型中 `Math.random()` 抽取,缺乏组合设计感。
2. **波次无节奏**5 波之后纯粹 6-12 随机数,难度曲线只靠数量与 `growthRatePerWave` 撑场。
3. **强化系统空转**`AffixType` 枚举定义 11 种词缀,但生成时 `affixes: []` 始终为空。
4. **可配置性差**:策划无法通过修改配置数据调整某一波的怪物组合与强化。
## 2. 重构目标
1. **新增多个怪物组合配置**以「小队Squad」为单位硬编码多种组合模板。
2. **硬编码多个强化配置**分为「波次级环境强化WaveEnchant」+「个体精英强化MonsterElite」双层。
3. **硬编码每波次配置**:每波包含 `基础怪物数 + 可选小队池 + 可选强化池` 三字段,组合方式可控。
4. **保持接口兼容**:调用方 [MissionMonComp.ts](../../../assets/script/game/map/MissionMonComp.ts) 改动最小化。
## 3. 设计决策
| 决策点 | 选择 | 理由 |
|---|---|---|
| 组合颗粒度 | **小队模块化**3-4 只怪/小队) | 每波拼装 2-4 个小队,变化丰富且可控 |
| 强化层级 | **双层混合**(波次级 + 个体级) | 既保留波次"环境"主题感,又有单体惊喜 |
| 波次配置组织 | **按波次逐条硬编码** | 每波字段完整独立,调参最直接 |
| 硬编码范围 | **wave 1~30 + 超出复用 wave 30** | 长线体验,超出后由全局乘子维持难度增长 |
| 个体强化种类 | **精选 5 种**Elite/Berserk/Shield/Swift/Regen | 数量适中,调参简单 |
| Boss 节奏 | **每 5 波一个 Boss**5/10/15/20/25/30 | 沿用现有仪式感 |
| 接口策略 | **同时重构接口** | 数据更干净,调用方改动可控 |
| 难度缩放 | **叠加波次级 HP/AP 乘子**(每 5 波 +15% HP / +8% AP | 与数量、强化叠加形成明显难度曲线 |
## 4. 整体架构与数据流
```
WaveConfig (硬编码 30 波)
├─ base_count 基础怪物数
├─ squad_pool 小队池 (引用 SquadConfig.id)
└─ enchant_pool 波次强化池 (引用 WaveEnchant.id)
┌───────────────────────────────────────────┐
│ RogueSpawningEngine.generateWave(n) │
│ 1. 取 WaveConfig │
│ 2. 计算全局 hp/ap 乘子 (波次递进 + Enchant) │
│ 3. 按权重从 squad_pool 抽小队拼装到 base_count │
│ 4. Boss 波首位放 Boss │
│ 5. 按 enchant_pool 抽 0~2 个 WaveEnchant │
│ 6. 每只小怪按概率抽个体 MonsterElite │
└───────────────────────────────────────────┘
GeneratedMonster[] (扩展接口)
MissionMonComp.addMonsterAtGrid
(读取 hp/ap 应用到 HeroAttrsComp)
```
**核心约束**
- 单波总怪数 ≤ 12网格上限3 行 4 列)
- Boss 占首位
- 超过 wave 30 用第 30 波配置 + 持续递增的全局乘子
## 5. 数据结构定义
### 5.1 个体强化MonsterElite
> **HeroAttrsComp 字段适配发现**:经检查 [HeroAttrsComp.ts](../../../assets/script/game/hero/HeroAttrsComp.ts) 无 `atk_cd` / `regen_per_sec` 字段。本次先不改造 HeroAttrsCompSwift/Regen 简化为「数值差异化」cd_mul 与 regen 作为后续扩展点留口子。
```typescript
export enum MonsterElite {
Elite = 0, // 精英:全面强化
Berserk = 1, // 狂暴:高攻低血
Shield = 2, // 护盾:坦克
Swift = 4, // 疾风:平衡偏输出
Regen = 3, // 再生:高血偏弱攻
}
export const MonsterEliteSet: Record<MonsterElite, {
name: string;
hp_mul: number;
ap_mul: number;
}> = {
[MonsterElite.Elite]: { name: "精英", hp_mul: 1.5, ap_mul: 1.3 },
[MonsterElite.Berserk]: { name: "狂暴", hp_mul: 0.9, ap_mul: 1.6 },
[MonsterElite.Shield]: { name: "护盾", hp_mul: 1.8, ap_mul: 0.8 },
[MonsterElite.Swift]: { name: "疾风", hp_mul: 1.0, ap_mul: 1.15 },
[MonsterElite.Regen]: { name: "再生", hp_mul: 1.3, ap_mul: 0.95 },
};
```
### 5.2 波次级强化WaveEnchant
```typescript
export interface WaveEnchant {
id: string;
name: string;
desc: string;
hp_mul?: number; // 全员 HP 乘子
ap_mul?: number; // 全员 AP 乘子
cd_mul?: number; // 全员攻击间隔乘子(后续扩展)
elite_rate_mul?: number; // 该波个体强化出现概率乘子
weight: number; // 在强化池中的抽取权重
}
export const WaveEnchantLibrary: Record<string, WaveEnchant> = {
frenzy: { id: "frenzy", name: "狂热浪潮", desc: "全员攻击力+25%", ap_mul: 1.25, weight: 10 },
ironhide: { id: "ironhide", name: "铁皮大军", desc: "全员生命+40%", hp_mul: 1.40, weight: 10 },
swift: { id: "swift", name: "疾风突袭", desc: "全员攻速+20%", cd_mul: 0.80, weight: 8 },
nightmare: { id: "nightmare", name: "梦魇来袭", desc: "个体强化率×2HP+15%", elite_rate_mul: 2.0, hp_mul: 1.15, weight: 5 },
fortress: { id: "fortress", name: "钢铁堡垒", desc: "全员 HP+60% AP-15%", hp_mul: 1.60, ap_mul: 0.85, weight: 6 },
};
```
### 5.3 小队模板SquadConfig
```typescript
export interface SquadSlot {
type: MonType;
count: number;
elite_chance?: number; // 0~1小队成员出现个体强化的概率默认 0
elite_pool?: MonsterElite[]; // 可选的个体强化池
}
export interface SquadConfig {
id: string;
name: string;
slots: SquadSlot[];
weight: number;
}
export const SquadLibrary: Record<string, SquadConfig> = {
melee_grunt: { id: "melee_grunt", name: "近战步兵组", weight: 10, slots: [{ type: MonType.Melee, count: 3 }] },
assassin_squad: { id: "assassin_squad", name: "刺客突袭组", weight: 6, slots: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Support, count: 1 }] },
mixed_balanced: { id: "mixed_balanced", name: "平衡混合组", weight: 8, slots: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Heavy, count: 1 }] },
long_line: { id: "long_line", name: "远程线列组", weight: 7, slots: [{ type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }] },
heavy_shield: { id: "heavy_shield", name: "重盾堡垒组", weight: 5, slots: [{ type: MonType.Heavy, count: 1 }, { type: MonType.Melee, count: 2 }] },
summoner_cult: { id: "summoner_cult", name: "召唤教派组", weight: 4, slots: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }] },
};
```
### 5.4 波次配置WaveConfig
```typescript
export interface WaveConfig {
base_count: number; // 该波基础怪物数 (1~12)
squad_pool: string[]; // 可选小队 id 池
enchant_pool?: string[]; // 可选波次强化 id 池(可空表示无强化)
boss_wave?: boolean; // 是否 Boss 波
elite_base_rate?: number; // 该波个体强化基础概率(默认按波次递增)
}
export const WaveConfigs: Record<number, WaveConfig> = {
// 1-4: 教学期
1: { base_count: 3, squad_pool: ["melee_grunt"] },
2: { base_count: 4, squad_pool: ["melee_grunt", "mixed_balanced"] },
3: { base_count: 5, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"] },
4: { base_count: 6, squad_pool: ["mixed_balanced", "long_line", "heavy_shield"] },
// 5: 第一 Boss
5: { base_count: 7, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], boss_wave: true },
// 6-9: 引入波次强化
6: { base_count: 7, squad_pool: ["assassin_squad", "long_line", "mixed_balanced"], enchant_pool: ["frenzy"], elite_base_rate: 0.10 },
7: { base_count: 8, squad_pool: ["melee_grunt", "heavy_shield", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.12 },
8: { base_count: 8, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.14 },
9: { base_count: 9, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.16 },
// 10: 第二 Boss
10: { base_count: 10, squad_pool: ["melee_grunt", "assassin_squad", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.18 },
// 11-14: 组合多样化
11: { base_count: 10, squad_pool: ["assassin_squad", "long_line", "summoner_cult", "mixed_balanced"], enchant_pool: ["swift", "frenzy"], elite_base_rate: 0.18 },
12: { base_count: 10, squad_pool: ["heavy_shield", "melee_grunt", "mixed_balanced", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.20 },
13: { base_count: 11, squad_pool: ["assassin_squad", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.22 },
14: { base_count: 11, squad_pool: ["heavy_shield", "long_line", "melee_grunt"], enchant_pool: ["ironhide", "swift"], elite_base_rate: 0.24 },
// 15: 第三 Boss中期高潮
15: { base_count: 11, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.25 },
// 16-24: 高压阶段
16: { base_count: 11, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.26 },
17: { base_count: 11, squad_pool: ["melee_grunt", "summoner_cult", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.27 },
18: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "heavy_shield"], enchant_pool: ["nightmare", "swift"], elite_base_rate: 0.28 },
19: { base_count: 12, squad_pool: ["mixed_balanced", "summoner_cult", "melee_grunt"], enchant_pool: ["fortress"], elite_base_rate: 0.28 },
20: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.30 },
21: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "summoner_cult"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.30 },
22: { base_count: 12, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.30 },
23: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "melee_grunt"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.32 },
24: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "long_line"], enchant_pool: ["fortress", "swift"], elite_base_rate: 0.32 },
// 25: 第四 Boss
25: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "heavy_shield"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.35 },
// 26-29: 终极阶段
26: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "long_line", "summoner_cult"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.35 },
27: { base_count: 12, squad_pool: ["heavy_shield", "mixed_balanced", "summoner_cult"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.38 },
28: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "melee_grunt"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.40 },
29: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "summoner_cult"], enchant_pool: ["frenzy", "ironhide", "fortress"], elite_base_rate: 0.42 },
// 30: 最终 Boss
30: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide", "swift", "nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.45 },
};
```
### 5.5 全局难度缩放
```typescript
export function getGlobalScale(wave: number): { hp_mul: number; ap_mul: number } {
const step = Math.floor((wave - 1) / 5); // 每 5 波一档
return {
hp_mul: 1 + 0.15 * step, // wave 1=1.0, 6=1.15, 11=1.30, 16=1.45, 21=1.60, 26=1.75
ap_mul: 1 + 0.08 * step, // wave 1=1.0, 6=1.08, 11=1.16, 16=1.24, 21=1.32, 26=1.40
};
}
```
**最终 HP 公式**`HeroInfo[uuid].hp × 全局乘子 × 波次Enchant乘子 × 个体Elite乘子`
## 6. 生成引擎算法
### 6.1 主流程伪代码
```typescript
generateWave(waveNumber: number): GeneratedMonster[] {
// 1. 取硬编码 WaveConfig (wave > 30 时使用第 30 波配置)
const cfg = WaveConfigs[Math.min(waveNumber, 30)];
// 2. 抽取波次级强化(按权重从 enchant_pool 抽 0~2 个)
const enchants = pickEnchants(cfg.enchant_pool);
// 3. 计算波次最终属性乘子
const globalScale = getGlobalScale(waveNumber);
const enchantHpMul = product(enchants.map(e => e.hp_mul ?? 1));
const enchantApMul = product(enchants.map(e => e.ap_mul ?? 1));
const eliteRateMul = product(enchants.map(e => e.elite_rate_mul ?? 1));
// 4. Boss 位Boss 波首位占 1 个)
let monsters: GeneratedMonster[] = [];
let remaining = cfg.base_count;
if (cfg.boss_wave) {
monsters.push(makeBoss(waveNumber));
remaining -= 1;
}
// 5. 小队拼装:按权重从 squad_pool 抽小队,累计到 remaining
monsters.push(...assembleSquads(cfg.squad_pool, remaining, waveNumber));
// 6. 应用全局 & 波次 Enchant 乘子
for (const m of monsters) {
m.hp = Math.round(m.hp * globalScale.hp_mul * enchantHpMul);
m.ap = Math.round(m.ap * globalScale.ap_mul * enchantApMul);
m.wave_enchants = enchants.map(e => e.id);
}
// 7. 应用个体 Elite非 Boss 怪)
const eliteBaseRate = cfg.elite_base_rate ?? Math.min(0.05 + waveNumber * 0.01, 0.30);
for (const m of monsters) {
if (!m.isBoss && Math.random() < eliteBaseRate * eliteRateMul) {
const elite = pickElite(m);
applyElite(m, elite);
m.elite = elite;
}
}
return monsters.slice(0, 12); // 硬上限保护
}
```
### 6.2 关键子算法
#### assembleSquads小队拼装
```typescript
function assembleSquads(pool: string[], target: number, wave: number): GeneratedMonster[] {
const result: GeneratedMonster[] = [];
let filled = 0;
let safety = 20; // 防止死循环
while (filled < target && safety-- > 0) {
const squad = pickWeighted(pool.map(id => SquadLibrary[id]));
const remaining = target - filled;
for (const slot of squad.slots) {
for (let i = 0; i < slot.count && filled < remaining; i++) {
result.push(makeMonster(slot.type, wave));
filled++;
}
}
}
return result;
}
```
- 小队塞不下时按剩余位截断
- `safety` 防御性循环上限
- 同一波可抽到重复小队(多样性来自权重 + 多次循环)
#### makeBossBoss 生成)
```typescript
function makeBoss(wave: number): GeneratedMonster {
const isMeleeBoss = Math.random() < 0.5;
const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss;
const uuid = pickUuidFromMonList(type);
// Boss 自带额外血量(基于波次的固定加成)
const baseHp = HeroInfo[uuid].hp;
const bossBonusHpMul = 1 + 0.20 * Math.floor((wave - 5) / 5); // wave 5=1.0, 10=1.2, 15=1.4...
return {
uuid, type,
hp: baseHp * bossBonusHpMul,
ap: HeroInfo[uuid].ap,
isBoss: true,
elite: undefined, // Boss 不带个体强化
affixes: [],
wave_enchants: [],
spawnIndex: 0,
};
}
```
#### pickEnchants波次强化抽取
```typescript
function pickEnchants(pool?: string[]): WaveEnchant[] {
if (!pool || pool.length === 0) return [];
const enchants: WaveEnchant[] = [];
// 第一个 Enchant: 70% 概率抽 1 个
if (Math.random() < 0.7) {
enchants.push(pickWeighted(pool.map(id => WaveEnchantLibrary[id])));
}
// 第二个 Enchant: 30% 概率再抽 1 个(不重复)
if (Math.random() < 0.3 && pool.length > 1) {
const second = pickWeighted(
pool.filter(id => !enchants.some(e => e.id === id))
.map(id => WaveEnchantLibrary[id])
);
if (second) enchants.push(second);
}
return enchants;
}
```
### 6.3 GeneratedMonster 扩展接口
```typescript
export interface GeneratedMonster {
uuid: number;
type: MonType;
hp: number;
ap: number;
isBoss: boolean;
spawnIndex: number;
/** @deprecated 已被 elite 取代,始终返回 [],仅向后兼容 */
affixes: AffixType[];
// 新增字段
elite?: MonsterElite; // 个体强化(无则 undefined
wave_enchants: string[]; // 该波触发的波次强化 id 列表
// 测试模式专用(保留原逻辑)
testSkills?: { ... };
}
```
### 6.4 关键设计权衡
| 决策 | 选择 | 理由 |
|---|---|---|
| Boss 是否可带个体强化 | 否 | Boss 已经有额外血量+波次加成 |
| Enchant 是否会叠加同种 | 否 | 第二个 Enchant 过滤已抽过的 id避免数值爆炸 |
| 小队截断 | 按剩余位截断 | 保证总怪数 = `base_count` |
| 个体 Elite 是否可叠加 | 否(每只最多 1 个) | 简化逻辑、避免叠加 bug |
| wave > 30 行为 | 复用 wave 30 配置 + 全局乘子继续增长 | 满足"长线"需求 |
## 7. 错误处理与配置校验
### 7.1 启动校验
```typescript
export function validateRogueConfig(): string[] {
const errors: string[] = [];
// 1. 校验 WaveConfigs 中所有 squad_pool / enchant_pool 引用存在
for (const wave in WaveConfigs) {
const cfg = WaveConfigs[wave];
for (const squadId of cfg.squad_pool) {
if (!SquadLibrary[squadId]) {
errors.push(`Wave ${wave} 引用了不存在的小队: ${squadId}`);
}
}
if (cfg.enchant_pool) {
for (const enchId of cfg.enchant_pool) {
if (!WaveEnchantLibrary[enchId]) {
errors.push(`Wave ${wave} 引用了不存在的强化: ${enchId}`);
}
}
}
if (cfg.base_count < 1 || cfg.base_count > 12) {
errors.push(`Wave ${wave} base_count=${cfg.base_count} 越界 (1~12)`);
}
}
// 2. 校验 SquadLibrary 中所有 type 在 MonList 中有怪
for (const id in SquadLibrary) {
const squad = SquadLibrary[id];
for (const slot of squad.slots) {
if (!MonList[slot.type] || MonList[slot.type].length === 0) {
errors.push(`小队 ${id} 引用的怪物类型 ${slot.type} 在 MonList 中无可用 uuid`);
}
}
}
return errors;
}
```
### 7.2 防御性兜底
| 异常情况 | 兜底行为 |
|---|---|
| `WaveConfigs[wave]` 不存在 | 使用 `WaveConfigs[30]` |
| `SquadLibrary[id]` 不存在 | 跳过该小队,记录 warning |
| `MonList[type]` 为空 | 使用 `MonList[MonType.Melee]` 兜底 |
| 单次 uuid 抽取失败 | 使用 `uuids[0]` 兜底 |
| 小队拼装超过 12 只 | 在主流程末尾统一 `slice(0, 12)` |
## 8. 向后兼容性
### 8.1 AffixType 枚举处理
```typescript
/** @deprecated 已被 MonsterElite 取代,仅保留导出避免破坏引用 */
export enum AffixType { ... }
```
经 Grep 确认 `AffixType` 仅在 RogueConfig.ts 内部使用。
### 8.2 接口保留
以下导出保留,调用方不受影响:
- `spawningEngine` 单例
- `getWaveMonsterCount(wave)` / `getWaveSlotConfig(wave)`
- `DefaultWaveSlot` / `WaveSlotConfig` (Proxy)
- `IWaveSlot` 接口
### 8.3 调用方影响评估
| 文件 | 引用情况 | 改造点 |
|---|---|---|
| [MissionMonComp.ts](../../../assets/script/game/map/MissionMonComp.ts) | `spawningEngine.generateWave()` / `GeneratedMonster` | 仅读 hp/ap/isBoss无字段依赖变更**零改动** |
| [MissionComp.ts](../../../assets/script/game/map/MissionComp.ts) | `spawningEngine.reset()` | **零改动** |
## 9. 测试模式适配
`TestModeConfig` 保留原拦截逻辑,仅扩展 2 个可选字段:
```typescript
export const TestModeConfig = {
enable: false,
// ... 原字段保留
affixes: [] as AffixType[],
// 新增:测试个体/波次强化的快速开关
testElite?: MonsterElite;
testWaveEnchant?: string;
};
```
测试模式生成的怪物补充新字段(`elite` / `wave_enchants`),保持接口一致性。
## 10. 文件结构(重构后骨架)
```
RogueConfig.ts
├─ 1. 文件头注释
├─ 2. 枚举与基础类型
│ ├─ MonsterElite + MonsterEliteSet
│ ├─ AffixType废弃保留
│ ├─ GeneratedMonster扩展
│ └─ IWaveSlot兼容保留
├─ 3. 怪物池(保留原逻辑)
│ └─ MonList从 HeroInfo 动态提取)
├─ 4. 波次强化库
│ ├─ WaveEnchant 接口
│ └─ WaveEnchantLibrary硬编码 5 条)
├─ 5. 小队模板库
│ ├─ SquadSlot / SquadConfig 接口
│ └─ SquadLibrary硬编码 6 条)
├─ 6. 波次配置表
│ ├─ WaveConfig 接口
│ └─ WaveConfigs硬编码 wave 1~30
├─ 7. 全局难度缩放
│ └─ getGlobalScale(wave)
├─ 8. 配置校验
│ └─ validateRogueConfig()
├─ 9. 生成引擎
│ ├─ RogueSpawningEngine重写
│ │ ├─ generateWave(wave)
│ │ ├─ assembleSquads()
│ │ ├─ makeBoss()
│ │ ├─ makeMonster()
│ │ ├─ applyElite()
│ │ ├─ pickElite()
│ │ ├─ pickEnchants()
│ │ └─ pickWeighted()
│ ├─ getWaveMonsterCount()(重写,返回 base_count
│ └─ getWaveSlotConfig()(保留,从生成结果反推)
├─ 10. 测试模式(保留)
│ └─ TestModeConfig扩展 testElite / testWaveEnchant
└─ 11. 全局单例 & 向后兼容导出
├─ spawningEngine
├─ DefaultWaveSlot
└─ WaveSlotConfig (Proxy)
```
## 11. 验证方式
1. **静态校验**:游戏启动时调用 `validateRogueConfig()`,控制台无 error。
2. **运行时验证**
- 测试模式分别设置 `testElite` / `testWaveEnchant`,验证 hp/ap 计算正确。
- 正常模式连续生成 wave 1~30检查每波怪物数、Boss 出现、Enchant 命中。
- wave 35 验证全局乘子继续递增。
3. **回归验证**:现有战斗流程不中断,[MissionMonComp](../../../assets/script/game/map/MissionMonComp.ts) 无报错。
## 12. 风险与缓解
| 风险 | 概率 | 缓解措施 |
|---|---|---|
| SquadLibrary 中 type 在 MonList 中无怪 | 中 | `validateRogueConfig()` 启动校验 + 兜底 Melee |
| 30 波数据量大,手写易出错 | 高 | 设计文档已给出完整 30 波数据,避免实施时现想 |
| MissionMonComp 隐式依赖 affixes | 低 | 已检查仅读 hp/ap/isBoss安全 |
| AffixType 在其他文件被引用 | 低 | Grep 已确认仅 RogueConfig.ts 内部使用 |
| 配置错误导致死循环 | 低 | assembleSquads 有 safety 计数器 |
## 13. 后续扩展点(本次不实施)
- **HeroAttrsComp 升级**:新增 `regen_per_sec` 字段后,启用 `MonsterElite.Regen` 的回血效果。
- **攻速字段统一**:把怪物 CD 也纳入 `getEffectiveSkillCd` 后,启用 `cd_mul`
- **数据驱动化**:把 WaveConfigs / SquadLibrary / WaveEnchantLibrary 抽到 JSON 配置文件,进一步解耦。
- **强化组合扩充**WaveEnchantLibrary 可继续扩充(如「元素浪潮」「血月」等主题强化)。