feat(怪物系统): 实现基于威胁预算的动态刷怪机制

新增威胁预算系统,根据游戏时间和英雄血量动态生成怪物
- 添加 HeroAttrsComp 查询获取英雄血量比例
- 实现 calculateBudget 计算当前威胁点数
- 实现 generateMonstersFromBudget 根据预算生成怪物
- 添加每秒刷怪逻辑到 MissionMonComp
- 定义不同时间段的怪物生成权重配置
This commit is contained in:
walkpan
2026-01-01 23:49:23 +08:00
parent 95f216e6e7
commit ad4fd30314
3 changed files with 227 additions and 9 deletions

View File

@@ -186,3 +186,152 @@ export function getStageMonConfigs(stage: number): IMonsConfig[] {
return monsterConfigs;
}
// ==========================================
// 新增基于威胁点数Threat Budget的刷怪系统
// ==========================================
// 怪物消耗点数配置
export const MonsterCost: Record<number, number> = {
5201: 1, // 兽人战士 (Warrior)
5301: 3, // 兽人斥候 (Assassin)
5401: 5, // 兽人卫士 (Tank)
5501: 4, // 兽人射手 (Remote)
5601: 10, // 兽人自爆兵 (Mechanic)
5602: 8, // 兽人召唤师
5603: 6, // 兽人祭司 (Healer)
5604: 6, // 兽人图腾师
5701: 50, // 兽人首领 (Elite/Boss)
};
// 刷怪权重接口
interface SpawnWeight {
uuid: number;
weight: number;
}
/**
* 根据游戏时间获取刷怪权重
* @param timeInSeconds 游戏时间(秒)
*/
function getSpawnWeights(timeInSeconds: number): SpawnWeight[] {
const minutes = timeInSeconds / 60;
if (minutes < 2) {
// 0-2min: 匀速群落 - 100% 战士
return [{ uuid: 5201, weight: 100 }];
} else if (minutes < 5) {
// 2-5min: 快速干扰 - 70% 战士, 30% 刺客
return [
{ uuid: 5201, weight: 70 },
{ uuid: 5301, weight: 30 }
];
} else if (minutes < 10) {
// 5-10min: 阵地博弈 - 50% 战士, 40% 刺客, 10% 攻城/治疗
return [
{ uuid: 5201, weight: 50 },
{ uuid: 5301, weight: 40 },
{ uuid: 5401, weight: 5 }, // 攻城
{ uuid: 5603, weight: 5 } // 治疗
];
} else if (minutes < 14) {
// 10-14min: 极限生存 - 30% 战士, 50% 刺客, 20% 机制/精英
return [
{ uuid: 5201, weight: 30 },
{ uuid: 5301, weight: 50 },
{ uuid: 5601, weight: 10 }, // 机制怪
{ uuid: 5701, weight: 10 } // 精英
];
} else {
// 15min: 剧情杀/决战 - 100% Boss
return [{ uuid: 5701, weight: 100 }];
}
}
/**
* 计算当前威胁点数预算
* @param timeInSeconds 游戏时间(秒)
* @param heroHpRatio 英雄血量比例 (0-1)
*/
export function calculateBudget(timeInSeconds: number, heroHpRatio: number = 1.0): number {
// 基础预算:每秒产生的点数
// 假设1分钟时为20点公式: Budget = Base * (1 + 60/60 * 0.2) = Base * 1.2 = 20 => Base ≈ 16
const Base_Budget = 16;
// 时间因子:随时间增加难度
const timeFactor = 1 + (timeInSeconds / 60) * 0.2;
// 强度乘数 (DDA):根据英雄状态调整
let intensityMultiplier = 1.0;
if (heroHpRatio < 0.4) {
// 绝地求生血量低于40%时预算缩减30%
intensityMultiplier = 0.7;
}
// 公式: Budget(t) = Base_Budget * (1 + t/60 * 0.2) * Intensity_Multiplier
return Math.floor(Base_Budget * timeFactor * intensityMultiplier);
}
/**
* 根据预算生成怪物列表
* @param timeInSeconds 游戏时间(秒)
* @param heroHpRatio 英雄血量比例
*/
export function generateMonstersFromBudget(timeInSeconds: number, heroHpRatio: number = 1.0): IMonsConfig[] {
// 15分钟后只生成Boss且如果已经有Boss可能需要控制这里简化为每次调用都尝试生成由外部控制频率或唯一性
// 但根据"清除所有杂兵锁定刷出最终BOSS"这里只返回Boss配置
if (timeInSeconds >= 15 * 60) {
// 可以在这里做特殊处理比如只返回一个Boss或者返回空如果Boss已存在
// 假设外部每秒调用这里返回Boss外部队列会堆积。
// 建议Boss波次由特殊逻辑控制或者这里返回Boss但消耗巨大预算
return [{
uuid: 5701,
type: MonType.BOSS,
level: Math.floor(timeInSeconds / 60), // 等级随时间
position: 2 // 中间位置
}];
}
const budget = calculateBudget(timeInSeconds, heroHpRatio);
const weights = getSpawnWeights(timeInSeconds);
const monsters: IMonsConfig[] = [];
let currentBudget = budget;
// 构建权重池
const pool: number[] = [];
weights.forEach(w => {
for(let i=0; i<w.weight; i++) pool.push(w.uuid);
});
if (pool.length === 0) return [];
let attempts = 0;
// 尝试消耗预算直到耗尽或无法购买最便宜的怪物
const minCost = Math.min(...weights.map(w => MonsterCost[w.uuid] || 1));
while (currentBudget >= minCost && attempts < 50) {
attempts++;
// 随机选择怪物
const uuid = pool[Math.floor(Math.random() * pool.length)];
const cost = MonsterCost[uuid] || 1;
if (currentBudget >= cost) {
currentBudget -= cost;
// 确定怪物类型
let type = MonType.NORMAL;
if (uuid === 5701) type = MonType.BOSS; // 或者是精英,视情况而定
else if (MonsterCost[uuid] >= 10) type = MonType.ELITE;
monsters.push({
uuid: uuid,
type: type,
level: Math.floor(timeInSeconds / 60) + 1, // 简单等级计算实际属性由getMonAttr处理
position: Math.floor(Math.random() * 5) // 随机位置 0-4
});
}
}
return monsters;
}