/** * @file RogueConfig.ts * @description 肉鸽刷怪系统(基于硬编码规则 + 模块化随机) * * 设计层次: * 1. MonsterElite - 个体强化(5 种) * 2. WaveEnchant - 波次级强化(环境修饰) * 3. SquadConfig - 小队模板(3-4 只怪的组合单元) * 4. WaveConfig - 每波硬编码(基础数 + 小队池 + 强化池) * 5. RogueSpawningEngine - 生成引擎(按规则组合上述配置) * * 详细设计见 docs/superpowers/specs/2026-07-05-rogue-config-refactor-design.md */ import { HeroInfo, MonType, MonTypeName } from "../common/config/heroSet"; import { FacSet } from "../common/config/GameSet"; // ======================== 1. 枚举与基础类型 ======================== /** * 个体强化类型(精选 5 种) * 设计权衡:HeroAttrsComp 暂无 atk_cd / regen 字段,因此 Swift/Regen 简化为数值差异化。 * 待 HeroAttrsComp 升级后可启用 cd_mul / regen 扩展点。 */ export enum MonsterElite { Elite = 0, // 精英:全面强化 Berserk = 1, // 狂暴:高攻低血 Shield = 2, // 护盾:坦克 Regen = 3, // 再生:高血偏弱攻 Swift = 4, // 疾风:平衡偏输出 } /** 个体强化效果定义 */ export const MonsterEliteSet: Record = { [MonsterElite.Elite]: { name: "精英", hp_mul: 1.5, ap_mul: 1.30 }, [MonsterElite.Berserk]: { name: "狂暴", hp_mul: 0.9, ap_mul: 1.60 }, [MonsterElite.Shield]: { name: "护盾", hp_mul: 1.8, ap_mul: 0.80 }, [MonsterElite.Regen]: { name: "再生", hp_mul: 1.3, ap_mul: 0.95 }, [MonsterElite.Swift]: { name: "疾风", hp_mul: 1.0, ap_mul: 1.15 }, }; /** * 旧版词缀类型枚举(已废弃) * @deprecated 已被 MonsterElite 取代,仅保留导出避免破坏外部引用。 * 新代码请使用 MonsterElite。 */ export enum AffixType { Elite = 0, Berserk = 1, Shield = 2, Regen = 3, Swift = 4, Giant = 5, Chain = 6, SummonerA = 7, CritRes = 8, FreezeRes = 9, KnockbackRes = 10, } // ======================== 2. 怪物 UUID 池 ======================== /** 按 MonType 分组的怪物 uuid 池,动态从 HeroInfo 提取,避免硬编码 */ export const MonList: Record = {}; for (const key in HeroInfo) { const info = HeroInfo[key]; if (info.fac === FacSet.MON && info.monType !== undefined) { if (!MonList[info.monType]) { MonList[info.monType] = []; } MonList[info.monType].push(info.uuid); } } // ======================== 3. 波次级强化库 ======================== /** * 波次级强化(环境修饰符) * 作用于整波怪物,与 MonsterElite(个体)解耦。 */ 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 = { 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: "个体强化率×2,HP+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 }, }; // ======================== 4. 小队模板库 ======================== /** 小队内单种怪物的槽位定义 */ export interface SquadSlot { type: MonType; count: number; } /** 小队模板(3-4 只怪的组合单元) */ export interface SquadConfig { id: string; name: string; slots: SquadSlot[]; weight: number; // 在小队池中的抽取权重 } /** 小队模板库(硬编码) */ export const SquadLibrary: Record = { 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. 波次配置表 ======================== /** 单波次完整配置 */ export interface WaveConfig { /** 基础怪物数(1~12) */ base_count: number; /** 可选小队 id 池,引擎按权重抽取拼装到 base_count */ squad_pool: string[]; /** 可选波次强化 id 池,按权重抽 0~2 个(可空表示无强化) */ enchant_pool?: string[]; /** 是否 Boss 波(首位放 Boss) */ boss_wave?: boolean; /** 该波个体强化基础概率(默认按波次递增) */ elite_base_rate?: number; } /** * 波次配置表(硬编码 wave 1~30) * 节奏曲线:教学期(1-4) → 第一Boss(5) → 引入强化(6-9) → 第二Boss(10) → * 组合多样化(11-14) → 第三Boss(15) → 高压阶段(16-24) → * 第四Boss(25) → 终极阶段(26-29) → 最终Boss(30) */ export const WaveConfigs: Record = { // ===== 教学期 ===== 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"] }, // ===== 第一 Boss ===== 5: { base_count: 7, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], boss_wave: true }, // ===== 引入波次强化 ===== 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 }, // ===== 第二 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: { 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 }, // ===== 第三 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: { 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 }, // ===== 第四 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: { 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 }, // ===== 最终 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 }, }; // ======================== 6. 全局难度缩放 ======================== /** * 根据波次返回全局 HP/AP 乘子,每 5 波递进一档 * @param wave 当前波次 * @returns hp_mul / ap_mul */ export function getGlobalScale(wave: number): { hp_mul: number; ap_mul: number } { const step = Math.floor((wave - 1) / 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 }; } // ======================== 7. 配置校验 ======================== /** * 校验 WaveConfigs / SquadLibrary / WaveEnchantLibrary 引用一致性 * 建议在游戏启动时调用一次,便于发现配置错误 * @returns 错误信息数组,空数组表示校验通过 */ export function validateRogueConfig(): string[] { const errors: string[] = []; // 1. 校验 WaveConfigs 引用与范围 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}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`); } } } return errors; } // ======================== 8. 生成结果接口 ======================== /** 生成结果(传递给 MissionMonComp 用于实例化怪物) */ export interface GeneratedMonster { uuid: number; type: MonType; hp: number; ap: number; isBoss: boolean; spawnIndex: number; /** @deprecated 已被 elite 取代,始终返回 [],仅向后兼容 */ affixes: AffixType[]; /** 个体强化(无则 undefined) */ elite?: MonsterElite; /** 该波触发的波次强化 id 列表 */ wave_enchants: string[]; /** 测试模式专用技能覆盖 */ testSkills?: { skill?: { s_uuid: number; cd?: number; overrides?: any }; atking?: { s_uuid: number; t_num: number; overrides?: any }[]; atked?: { s_uuid: number; t_num: number; overrides?: any }[]; dead?: { s_uuid: number; t_num: number; overrides?: any }[]; fstart?: { s_uuid: number; t_num: number; overrides?: any }[]; fend?: { s_uuid: number; t_num: number; overrides?: any }[]; }; } // ======================== 9. 测试模式配置 ======================== export const TestModeConfig = { enable: false, // 默认关闭测试模式 baseHp: 150, baseAp: 12, growthRatePerWave: 0.2, monType: MonType.Melee, monUuid: 6001, /** @deprecated 已被 testElite 取代 */ affixes: [] as AffixType[], spawnCount: 1, /** 测试个体强化 */ testElite: undefined as MonsterElite | undefined, /** 测试波次强化 id */ testWaveEnchant: undefined as string | undefined, skill: undefined as { s_uuid: number; cd?: number; overrides?: any } | undefined, atking: undefined as { s_uuid: number; t_num: number; overrides?: any }[] | undefined, atked: undefined as { s_uuid: number; t_num: number; overrides?: any }[] | undefined, dead: undefined as { s_uuid: number; t_num: number; overrides?: any }[] | undefined, fstart: undefined as { s_uuid: number; t_num: number; overrides?: any }[] | undefined, fend: undefined as { s_uuid: number; t_num: number; overrides?: any }[] | undefined, }; // ======================== 10. 生成引擎 ======================== /** * 肉鸽刷怪生成引擎 * 按硬编码 WaveConfig 规则,组合小队模板与双层强化系统生成怪物列表 */ export class RogueSpawningEngine { /** * 生成指定波次的怪物列表 * @param waveNumber 波次(1 起,>30 时复用 wave 30 配置) * @returns 怪物列表,长度 ≤ 12 */ generateWave(waveNumber: number): GeneratedMonster[] { if (waveNumber < 1) return []; // 测试模式拦截:完全绕过生成引擎,直接返回硬编码测试怪 if (TestModeConfig.enable) { return this.generateTestWave(waveNumber); } // 1. 取硬编码 WaveConfig(>30 时复用 wave 30 配置) const cfg = WaveConfigs[Math.min(waveNumber, 30)]; // 2. 抽取波次级强化(按权重从 enchant_pool 抽 0~2 个) const enchants = this.pickEnchants(cfg.enchant_pool); // 3. 计算波次最终属性乘子 const globalScale = getGlobalScale(waveNumber); const enchantHpMul = enchants.reduce((m, e) => m * (e.hp_mul ?? 1), 1); const enchantApMul = enchants.reduce((m, e) => m * (e.ap_mul ?? 1), 1); const eliteRateMul = enchants.reduce((m, e) => m * (e.elite_rate_mul ?? 1), 1); // 4. Boss 位(Boss 波首位占 1 个) const monsters: GeneratedMonster[] = []; let remaining = cfg.base_count; if (cfg.boss_wave) { monsters.push(this.makeBoss(waveNumber)); remaining -= 1; } // 5. 小队拼装:按权重从 squad_pool 抽小队,累计到 remaining const squadMonsters = this.assembleSquads(cfg.squad_pool, remaining, waveNumber); monsters.push(...squadMonsters); // 6. 应用全局 & 波次 Enchant 乘子 const waveEnchantIds = enchants.map(e => e.id); for (const m of monsters) { m.hp = Math.max(1, Math.round(m.hp * globalScale.hp_mul * enchantHpMul)); m.ap = Math.max(1, Math.round(m.ap * globalScale.ap_mul * enchantApMul)); m.wave_enchants = waveEnchantIds.slice(); } // 7. 应用个体 Elite(非 Boss 怪,每只最多 1 个) 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 = this.pickElite(); this.applyElite(m, elite); } } // 8. 硬上限保护(理论不会触发,但防止配置错误) return monsters.slice(0, 12); } /** 重置引擎状态(保留接口兼容,当前无内部状态) */ reset(): void { // 无可变状态 } /** * 获取指定波次的怪物总数 * @param waveNumber 目标波数 * @returns 预计生成的怪物总数(base_count,受 12 上限约束) */ getWaveMonsterCount(waveNumber: number): number { if (waveNumber < 1) return 0; if (TestModeConfig.enable) { return Math.max(1, TestModeConfig.spawnCount || 1); } const cfg = WaveConfigs[Math.min(waveNumber, 30)]; return Math.min(cfg.base_count, 12); } /** * 获取波次槽位配置(向后兼容接口) * 内部从 generateWave 反推,仅用于历史调用方 * @param waveNumber 目标波数 */ getWaveSlotConfig(waveNumber: number): IWaveSlot[] { const generated = this.generateWave(waveNumber); const slotMap = new Map(); for (const m of generated) { const existing = slotMap.get(m.type); if (existing) { existing.count++; } else { slotMap.set(m.type, { count: 1, affixes: [] }); } } return Array.from(slotMap.entries()).map(([type, data]) => ({ type, count: data.count, ...(data.affixes.length > 0 ? { affixes: data.affixes } : {}), })); } // ======================== 私有生成子算法 ======================== /** 测试模式:完全绕过引擎逻辑 */ private generateTestWave(waveNumber: number): GeneratedMonster[] { const growth = 1 + (waveNumber - 1) * TestModeConfig.growthRatePerWave; const count = Math.max(1, TestModeConfig.spawnCount || 1); const monsters: GeneratedMonster[] = []; for (let i = 0; i < count; i++) { monsters.push({ uuid: TestModeConfig.monUuid, type: TestModeConfig.monType, hp: Math.round(TestModeConfig.baseHp * growth), ap: Math.round(TestModeConfig.baseAp * growth), affixes: [...TestModeConfig.affixes], isBoss: false, spawnIndex: i, elite: TestModeConfig.testElite, wave_enchants: TestModeConfig.testWaveEnchant ? [TestModeConfig.testWaveEnchant] : [], testSkills: { skill: TestModeConfig.skill, atking: TestModeConfig.atking, atked: TestModeConfig.atked, dead: TestModeConfig.dead, fstart: TestModeConfig.fstart, fend: TestModeConfig.fend, }, }); } return monsters; } /** * 小队拼装:按权重从 squad_pool 抽小队,累计到 target 数量 * 超出部分按剩余位截断 */ private assembleSquads(pool: string[], target: number, wave: number): GeneratedMonster[] { const result: GeneratedMonster[] = []; let filled = 0; let safety = 20; // 防御性循环上限 while (filled < target && safety-- > 0) { const squad = this.pickWeightedSquad(pool); if (!squad) break; const remaining = target - filled; for (const slot of squad.slots) { for (let i = 0; i < slot.count && filled < remaining; i++) { result.push(this.makeMonster(slot.type, wave, filled)); filled++; } if (filled >= remaining) break; } } return result; } /** 生成 Boss(首位) */ private makeBoss(wave: number): GeneratedMonster { const isMeleeBoss = Math.random() < 0.5; const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss; // Boss 类型兜底:若 MonList 缺失则退回 MeleeBoss let uuids = MonList[type]; if (!uuids || uuids.length === 0) { uuids = MonList[MonType.MeleeBoss] || MonList[MonType.Melee] || [6001]; } const uuid = uuids[Math.floor(Math.random() * uuids.length)]; const baseInfo = HeroInfo[uuid]; const baseHp = baseInfo ? baseInfo.hp : 500; const baseAp = baseInfo ? baseInfo.ap : 30; // Boss 自带额外血量加成:每 5 波递增 20%(wave 5=1.0, 10=1.2, 15=1.4...) const bossBonusHpMul = 1 + 0.20 * Math.floor(Math.max(0, wave - 5) / 5); return { uuid, type, hp: Math.round(baseHp * bossBonusHpMul), ap: baseAp, affixes: [], // 兼容字段 isBoss: true, spawnIndex: 0, wave_enchants: [], // 后续统一填充 }; } /** 生成普通怪物 */ private makeMonster(type: MonType, wave: number, spawnIndex: number): GeneratedMonster { let uuids = MonList[type]; if (!uuids || uuids.length === 0) { // 兜底 Melee uuids = MonList[MonType.Melee] || [6001]; } const uuid = uuids[Math.floor(Math.random() * uuids.length)]; const baseInfo = HeroInfo[uuid]; const baseHp = baseInfo ? baseInfo.hp : 100; const baseAp = baseInfo ? baseInfo.ap : 10; return { uuid, type, hp: baseHp, ap: baseAp, affixes: [], // 兼容字段 isBoss: false, spawnIndex, wave_enchants: [], // 后续统一填充 }; } /** 抽取波次级强化(按权重抽 0~2 个,不重复) */ private pickEnchants(pool?: string[]): WaveEnchant[] { if (!pool || pool.length === 0) return []; const enchants: WaveEnchant[] = []; // 第一个 Enchant: 70% 概率抽 1 个 if (Math.random() < 0.7) { const first = this.pickWeightedEnchant(pool); if (first) enchants.push(first); } // 第二个 Enchant: 30% 概率再抽 1 个(不重复) if (Math.random() < 0.3 && pool.length > 1) { const remaining = pool.filter(id => !enchants.some(e => e.id === id)); const second = this.pickWeightedEnchant(remaining); if (second) enchants.push(second); } return enchants; } /** 抽取个体强化(等概率随机 1 种) */ private pickElite(): MonsterElite { const candidates: MonsterElite[] = [ MonsterElite.Elite, MonsterElite.Berserk, MonsterElite.Shield, MonsterElite.Swift, MonsterElite.Regen, ]; return candidates[Math.floor(Math.random() * candidates.length)]; } /** 应用个体强化到怪物 */ private applyElite(m: GeneratedMonster, elite: MonsterElite): void { const def = MonsterEliteSet[elite]; m.hp = Math.max(1, Math.round(m.hp * def.hp_mul)); m.ap = Math.max(1, Math.round(m.ap * def.ap_mul)); m.elite = elite; } /** 按权重从小队 id 池抽 1 个小队 */ private pickWeightedSquad(pool: string[]): SquadConfig | null { const valid = pool.map(id => SquadLibrary[id]).filter(s => s); if (valid.length === 0) return null; return this.pickWeighted(valid); } /** 按权重从强化 id 池抽 1 个强化 */ private pickWeightedEnchant(pool: string[]): WaveEnchant | null { const valid = pool.map(id => WaveEnchantLibrary[id]).filter(e => e); if (valid.length === 0) return null; return this.pickWeighted(valid); } /** 通用加权随机抽取 */ private pickWeighted(items: T[]): T | null { if (items.length === 0) return null; const total = items.reduce((s, it) => s + it.weight, 0); if (total <= 0) return items[Math.floor(Math.random() * items.length)]; let r = Math.random() * total; for (const it of items) { r -= it.weight; if (r <= 0) return it; } return items[items.length - 1]; } } // ======================== 11. 全局单例 & 向后兼容导出 ======================== export const spawningEngine = new RogueSpawningEngine(); /** 历史接口:波次槽位(保留兼容) */ export interface IWaveSlot { type: number; count: number; /** @deprecated 已废弃,新接口不再使用 */ affixes?: AffixType[]; } /** * 获取指定波次预计的怪物总数量 * @param waveNumber 目标波数 */ export function getWaveMonsterCount(waveNumber: number): number { return spawningEngine.getWaveMonsterCount(waveNumber); } /** 获取波次槽位配置(向后兼容) */ export function getWaveSlotConfig(waveNumber: number): IWaveSlot[] { return spawningEngine.getWaveSlotConfig(waveNumber); } export const DefaultWaveSlot: IWaveSlot[] = [ { type: MonType.Melee, count: 20 }, { type: MonType.Long, count: 15 }, { type: MonType.Support, count: 5 }, ]; /** 波次槽位配置代理(按需生成) */ export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = new Proxy( {} as { [wave: number]: IWaveSlot[] }, { get(_target, prop: string) { const wave = parseInt(prop, 10); if (!isNaN(wave) && wave >= 1) { return spawningEngine.getWaveSlotConfig(wave); } if (prop === "toJSON") return () => ({}); return undefined; }, has(_target, prop: string) { const wave = parseInt(prop, 10); return !isNaN(wave) && wave >= 1; }, } );