feat(怪物系统): 实现基于威胁预算的动态刷怪机制
新增威胁预算系统,根据游戏时间和英雄血量动态生成怪物 - 添加 HeroAttrsComp 查询获取英雄血量比例 - 实现 calculateBudget 计算当前威胁点数 - 实现 generateMonstersFromBudget 根据预算生成怪物 - 添加每秒刷怪逻辑到 MissionMonComp - 定义不同时间段的怪物生成权重配置
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user