Files
pixelheros/assets/script/game/map/RogueConfig.ts
pan 6c2f1defa9 refactor(monster): 重构波次怪物管理逻辑,优化配置与流程
1.  更新怪物配置注释,修正近战/远程怪物的描述与分类
2.  移除MissionComp中过时的自适应刷怪逻辑
3.  重构MissionMonComp:删除插队刷怪队列、简化波次流程、统一怪物生成逻辑
4.  移除冗余日志与注释,优化代码可读性
5.  调整波次准备阶段的怪物生成时机与数据处理
2026-06-23 17:05:07 +08:00

304 lines
9.6 KiB
TypeScript
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.
/**
* @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<number, string> = {
[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<number, number[]> = {
[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<number, { count: number; affixes: AffixType[] }>();
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;
},
}
);