Files
pixelheros/assets/script/game/map/RogueConfig.ts
pan 257e0b4870 refactor(map): 重构怪物池配置,改为动态提取
将硬编码的怪物列表改为从HeroInfo动态获取,移除硬编码的怪物UUID配置,同时引入FacSet依赖来区分怪物阵营
2026-07-03 17:13:52 +08:00

282 lines
9.1 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, MonType, MonTypeName } from "../common/config/heroSet";
import { FacSet } from "../common/config/GameSet";
// ======================== 词缀类型枚举 ========================
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[]> = {};
// 动态从 HeroInfo 中提取怪物分类,避免硬编码
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);
}
}
// ======================== 测试模式配置 ========================
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;
},
}
);