/** * @file RogueConfig.ts * @description 肉鸽刷怪系统 (重构精简版) * * 核心规则: * 1. 最大怪物数量 12 个 * 2. 1-4 波:怪物数量逐步增加(如 1, 2, 4, 6) * 3. 第 5 波:出现第一个 Boss * 4. 5 波之后:随机出现 6-12 个怪物 * 5. 强度:仅通过怪物数量和波次(等级)来逐步提升,每波增加一定比例基础属性。 */ import { HeroInfo } from "../common/config/heroSet"; // ======================== 怪物类型枚举 ======================== export enum MonType { Melee = 0, Heavy = 1, Long = 2, Support = 3, Summoner = 5, Assassin = 6, MeleeBoss = 8, LongBoss = 9, } export const MonTypeName: Record = { [MonType.Melee]: "近战", [MonType.Heavy]: "重型", [MonType.Long]: "远程", [MonType.Support]: "辅助", [MonType.Summoner]: "召唤师", [MonType.Assassin]: "刺客", [MonType.MeleeBoss]: "近战Boss", [MonType.LongBoss]: "远程Boss", } // ======================== 词缀类型枚举 ======================== 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, } // ======================== 怪物 UUID 池 ======================== export const MonList: Record = { [MonType.Melee]: [6001, 6002], [MonType.Heavy]: [6003], [MonType.Long]: [6004], [MonType.Support]: [6007], [MonType.Summoner]: [6008], [MonType.Assassin]: [6005], [MonType.MeleeBoss]: [6006, 6102, 6104, 6106], [MonType.LongBoss]: [6101, 6103, 6105], } // ======================== 测试模式配置 ======================== export const TestModeConfig = { enable: false, // 默认关闭测试模式 baseHp: 150, baseAp: 12, growthRatePerWave: 0.2, monType: MonType.Melee, monUuid: 6001, affixes: [] as AffixType[], spawnCount: 1, 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, } // ======================== 生成结果接口 ======================== export interface GeneratedMonster { uuid: number type: MonType hp: number ap: number affixes: AffixType[] isBoss: boolean spawnIndex: number 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 }[]; } } // ======================== 生成引擎 ======================== export class RogueSpawningEngine { generateWave(waveNumber: number): GeneratedMonster[] { if (waveNumber < 1) return []; // 测试模式拦截 if (TestModeConfig.enable) { 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, testSkills: { skill: TestModeConfig.skill, atking: TestModeConfig.atking, atked: TestModeConfig.atked, dead: TestModeConfig.dead, fstart: TestModeConfig.fstart, fend: TestModeConfig.fend } }); } return monsters; } // 1. 确定生成数量 let count = this.getWaveMonsterCount(waveNumber); // 第 5 波之后,在 count 的基础上加上随机逻辑 (覆盖原有固定返回值) if (waveNumber > 5 && !TestModeConfig.enable) { count = Math.floor(Math.random() * 7) + 6; } // 限制最大怪物数为 12 count = Math.min(count, 12); // 每 5 波出现一次 Boss const isBossWave = (waveNumber % 5 === 0); const monsters: GeneratedMonster[] = []; // 普通怪池 const normalTypes = [ MonType.Melee, MonType.Heavy, MonType.Long, MonType.Support, MonType.Assassin, MonType.Summoner ]; for (let i = 0; i < count; i++) { let type: MonType; let isBoss = false; if (isBossWave && i === 0) { // 生成 Boss isBoss = true; type = Math.random() > 0.5 ? MonType.MeleeBoss : MonType.LongBoss; } else { // 生成普通小怪 type = normalTypes[Math.floor(Math.random() * normalTypes.length)]; } const uuids = MonList[type] || MonList[MonType.Melee]; const uuid = uuids[Math.floor(Math.random() * uuids.length)]; // 从 HeroInfo 中获取基础属性,如果没找到则给一个兜底值 const baseInfo = HeroInfo[uuid]; const baseHp = baseInfo ? baseInfo.hp : 100; const baseAp = baseInfo ? baseInfo.ap : 10; const finalHp = Math.max(1, Math.round(baseHp)); const finalAp = Math.max(1, Math.round(baseAp)); monsters.push({ uuid, type, hp: finalHp, ap: finalAp, affixes: [], // 词缀系统已移除,传入空数组 isBoss: isBoss, spawnIndex: i, }); } return monsters; } reset(): void { // 重置引擎状态 } /** * 导出一个空函数,用于外部获取/预测某个波次的怪物数量 * @param waveNumber 目标波数 * @returns 预计生成的怪物总数 */ getWaveMonsterCount(waveNumber: number): number { if (waveNumber < 1) return 0; if (TestModeConfig.enable) { return Math.max(1, TestModeConfig.spawnCount || 1); } let count = 0; if (waveNumber === 1) { count = 1; } else if (waveNumber === 2) { count = 2; } else if (waveNumber === 3) { count = 4; } else if (waveNumber === 4) { count = 6; } else if (waveNumber === 5) { count = 6; } else { // 注意:由于后续波次是 6-12 随机,这里可以返回一个平均值或特定的空逻辑 // 根据需求,暂时返回固定的 6,或者保留外部自己实现逻辑的口子 count = 6; } return Math.min(count, 12); } // 向后兼容接口 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: m.affixes }); } } return Array.from(slotMap.entries()).map(([type, data]) => ({ type, count: data.count, ...(data.affixes.length > 0 ? { affixes: data.affixes } : {}), })); } } // ======================== 全局单例 & 向后兼容导出 ======================== export const spawningEngine = new RogueSpawningEngine(); export interface IWaveSlot { type: number count: number 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; }, } );