Files
pixelheros/assets/script/game/map/RogueConfig.ts
walkpan ad4fd30314 feat(怪物系统): 实现基于威胁预算的动态刷怪机制
新增威胁预算系统,根据游戏时间和英雄血量动态生成怪物
- 添加 HeroAttrsComp 查询获取英雄血量比例
- 实现 calculateBudget 计算当前威胁点数
- 实现 generateMonstersFromBudget 根据预算生成怪物
- 添加每秒刷怪逻辑到 MissionMonComp
- 定义不同时间段的怪物生成权重配置
2026-01-01 23:49:23 +08:00

338 lines
11 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.
/**
* 肉鸽模式配置脚本 - 增强版
*
* 功能说明:
* - 提供基础的刷怪配置:刷什么怪,刷多少怪
* - 支持程序化关卡生成逻辑,每一关的怪物组合、数量和强度应随关卡进度递增而变化
* - 支持随机事件系统
*
*
* 3. 全局动态 Scaling 算法既然是 15 分钟单局,属性不应一成不变。
* 建议引入 Wave_Factor (波次因子)
* 公式建议: Current_Stat = Base_Stat * (1 + (time / 60) * 0.15) ^ Growth_Type
* •HP 成长: 设为指数级 (Growth_Type = 1.2)。后期怪物血量会呈几何倍数增加,适配『神装英雄』。
* •AP 成长: 设为线性 (Growth_Type = 1.0)。保证怪物能击穿后期护盾,但不会一击必杀。
* •Speed 成长: 设为对数级 (Growth_Type = 0.5)。防止后期怪物速度过快导致画面瞬移。
*
* @author 游戏开发团队
* @version 2.0 增强版
* @date 2025-10-19
*/
import { HeroInfo } from "../common/config/heroSet";
// 精英怪物配置表
export const EliteMons = [ 5201, 5202, 5203, 5213 ];
// Boss怪物配置表
export const BossMons = [ 5201, 5202, ];
export enum IMons{ ORC=1, HUMAN=2, ELF=3,}
export const Mons={
[IMons.ORC]:[5201,5202],
[IMons.HUMAN]:[5201,5202],
[IMons.ELF]:[5201,5202]
}
/**
* 怪物类型枚举
*/
export enum MonType {
NORMAL = 0, // 普通怪物
ELITE = 1, // 精英怪物
BOSS = 2 // Boss怪物
}
/**
* 怪物配置接口
*/
export interface IMonsConfig {
uuid: number; // 怪物ID
type: MonType; // 怪物类型
level: number; // 等级
position?: number; // 位置(可选)
buffs?: any[]; // buff列表可选
}
/**
* 怪物属性接口
*/
export interface MonAttrs {
hp: number;
mp: number;
ap: number;
def: number;
speed: number;
}
/**
* 成长类型枚举
*/
enum GrowthType {
EXPONENTIAL = 1.2, // 指数级 - HP
LINEAR = 1.0, // 线性 - AP
LOGARITHMIC = 0.5 // 对数级 - Speed
}
/**
* 计算波次因子
* @param stage 当前波次
* @param timeInSeconds 游戏进行时间(秒)
* @returns 波次因子 (0-1之间15分钟时达到最大)
*/
function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number {
const MAX_GAME_TIME = 15 * 60; // 15分钟 = 900秒
const effectiveTime = timeInSeconds || (stage * 30); // 如果没有时间数据用波次估算每波30秒
const factor = Math.min(effectiveTime / MAX_GAME_TIME, 1.0);
return factor;
}
/**
* 应用成长公式到基础属性
* @param baseStat 基础属性值
* @param waveFactor 波次因子 (0-1)
* @param growthType 成长类型
* @returns 成长后的属性值
*/
function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number {
// 公式: Current_Stat = Base_Stat * (1 + waveFactor * 0.15) ^ Growth_Type
const growthMultiplier = Math.pow(1 + waveFactor * 0.15, growthType);
return Math.floor(baseStat * growthMultiplier);
}
/**
* 获取怪物动态成长属性
* @param stage 当前波次
* @param uuid 怪物ID
* @param monType 怪物类型
* @param timeInSeconds 游戏进行时间(秒)
* @returns 怪物属性
*/
export function getMonAttr(stage: number, uuid: number, monType: MonType = MonType.NORMAL, timeInSeconds: number = 0): MonAttrs {
const baseMonster = HeroInfo[uuid];
if (!baseMonster) {
console.warn(`[RogueConfig] 未找到怪物ID: ${uuid}`);
return { hp: 100, mp: 100, ap: 10, def: 0, speed: 100 };
}
// 计算波次因子
const waveFactor = calculateWaveFactor(stage, timeInSeconds);
// 根据怪物类型应用额外的倍率
let typeMultiplier = 1.0;
if (monType === MonType.ELITE) {
typeMultiplier = 2.0; // 精英怪2倍属性
} else if (monType === MonType.BOSS) {
typeMultiplier = 5.0; // Boss 5倍属性
}
// 应用不同的成长类型
const hp = applyGrowthFormula(baseMonster.hp, waveFactor, GrowthType.EXPONENTIAL) * typeMultiplier;
const ap = applyGrowthFormula(baseMonster.ap, waveFactor, GrowthType.LINEAR) * typeMultiplier;
const speed = applyGrowthFormula(baseMonster.speed, waveFactor, GrowthType.LOGARITHMIC);
// MP和DEF使用线性成长
const mp = applyGrowthFormula(baseMonster.mp, waveFactor, GrowthType.LINEAR);
const def = applyGrowthFormula(baseMonster.def, waveFactor, GrowthType.LINEAR) * typeMultiplier;
return {
hp: Math.floor(hp),
mp: Math.floor(mp),
ap: Math.floor(ap),
def: Math.floor(def),
speed: Math.floor(speed)
};
}
/**
* 根据波次生成怪物配置
* @param stage 当前波次
* @returns IMonsConfig数组
*/
export function getStageMonConfigs(stage: number): IMonsConfig[] {
const monsterConfigs: IMonsConfig[] = [];
// 基础怪物列表从heroset.ts中获取
const normalMons = [5201, 5301, 5401, 5501, 5601, 5602, 5603, 5604];
const eliteMons = [5701];
// 根据波次生成怪物配置
// 波次越高,怪物数量越多,精英怪物出现概率越高
const baseCount = 5 + Math.floor(stage / 2); // 基础数量每2波增加1
const eliteChance = Math.min(stage * 0.05, 0.3); // 精英怪概率最高30%
const bossWave = stage % 10 === 0; // 每10波出Boss
if (bossWave && stage > 0) {
// Boss波
monsterConfigs.push({
uuid: 5701,
type: MonType.BOSS,
level: stage
});
} else {
// 普通波
for (let i = 0; i < baseCount; i++) {
// 随机决定是否生成精英怪
const isElite = Math.random() < eliteChance;
const monList = isElite ? eliteMons : normalMons;
const randomUuid = monList[Math.floor(Math.random() * monList.length)];
monsterConfigs.push({
uuid: randomUuid,
type: isElite ? MonType.ELITE : MonType.NORMAL,
level: stage,
position: i % 5
});
}
}
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;
}