339 lines
11 KiB
TypeScript
339 lines
11 KiB
TypeScript
/**
|
||
* 肉鸽模式配置脚本 - 增强版
|
||
*
|
||
* 功能说明:
|
||
* - 提供基础的刷怪配置:刷什么怪,刷多少怪
|
||
* - 支持程序化关卡生成逻辑,每一关的怪物组合、数量和强度应随关卡进度递增而变化
|
||
* - 支持随机事件系统
|
||
*
|
||
*
|
||
* 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
|
||
// 修改:降低基础预算,避免队列积压。每秒 1.5 点左右(减半)。
|
||
const Base_Budget = 1.5;
|
||
|
||
// 时间因子:随时间增加难度
|
||
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;
|
||
}
|