1. 更新怪物配置注释,修正近战/远程怪物的描述与分类 2. 移除MissionComp中过时的自适应刷怪逻辑 3. 重构MissionMonComp:删除插队刷怪队列、简化波次流程、统一怪物生成逻辑 4. 移除冗余日志与注释,优化代码可读性 5. 调整波次准备阶段的怪物生成时机与数据处理
304 lines
9.6 KiB
TypeScript
304 lines
9.6 KiB
TypeScript
/**
|
||
* @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;
|
||
},
|
||
}
|
||
);
|