调整游戏核心平衡参数以优化15分钟游戏体验: 1. 提升抽卡和升级金币消耗(CHOU_GOLD 5→100,LVUP_GOLD 10→50) 2. 重制英雄基础属性和成长值(战士HP 200→300,法师AP 14→40) 3. 优化怪物生成逻辑和属性曲线(BOSS HP 25000→2000) 4. 更新经济系统公式和波次权重配置
425 lines
13 KiB
TypeScript
425 lines
13 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 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.15, // 指数级 - HP
|
||
LINEAR = 1.05, // 线性 - AP
|
||
LOGARITHMIC = 0.3 // 对数级 - Speed
|
||
}
|
||
|
||
|
||
/**
|
||
* 全局刷怪配置接口
|
||
*/
|
||
export interface IRogueGlobalConfig {
|
||
/** 场上最大怪物数量限制 */
|
||
maxMonsterCount: number;
|
||
/** 刷怪逻辑执行间隔(秒) - 决定多久计算一次生成 */
|
||
spawnLogicInterval: number;
|
||
/** 单个怪物生成间隔(秒) - 队列中怪物的实际生成频率 */
|
||
spawnInterval: number;
|
||
/** 基础威胁预算 - 每秒产生的基础点数 */
|
||
baseBudget: number;
|
||
/** 时间难度因子 - 每分钟增加的预算比例 (0.2 = 20%) */
|
||
timeDifficultyFactor: number;
|
||
/** 绝地求生阈值 - 英雄血量低于此比例时触发减缓刷怪 */
|
||
survivalHpThreshold: number;
|
||
/** 绝地求生预算乘数 - 触发阈值时的预算折扣 */
|
||
survivalBudgetMultiplier: number;
|
||
/** 单次逻辑最大生成怪物数限制 - 防止瞬间生成过多 */
|
||
maxSpawnPerLogic: number;
|
||
}
|
||
|
||
/**
|
||
* 默认配置
|
||
*/
|
||
export const DefaultRogueConfig: IRogueGlobalConfig = {
|
||
maxMonsterCount: 50, // 默认同屏50只
|
||
spawnLogicInterval: 1.0, // 每秒计算一次
|
||
spawnInterval: 0.6, // 队列出怪间隔0.6秒
|
||
baseBudget: 5.0, // 基础预算
|
||
timeDifficultyFactor: 0.5, // 每分钟增加50%预算
|
||
survivalHpThreshold: 0.4, // 40%血量触发保护
|
||
survivalBudgetMultiplier: 0.7, // 保护时预算打7折
|
||
maxSpawnPerLogic: 5 // 单次最多生成5只
|
||
};
|
||
|
||
// 当前配置实例
|
||
let currentConfig: IRogueGlobalConfig = { ...DefaultRogueConfig };
|
||
|
||
/**
|
||
* 获取当前全局配置
|
||
*/
|
||
export function getRogueConfig(): IRogueGlobalConfig {
|
||
return currentConfig;
|
||
}
|
||
|
||
/**
|
||
* 更新全局配置
|
||
* @param config 部分或全部配置
|
||
*/
|
||
export function updateRogueConfig(config: Partial<IRogueGlobalConfig>) {
|
||
currentConfig = {
|
||
...currentConfig,
|
||
...config
|
||
};
|
||
console.log("[RogueConfig] Configuration updated:", currentConfig);
|
||
}
|
||
|
||
/**
|
||
* 计算当前威胁点数预算
|
||
* @param timeInSeconds 游戏时间(秒)
|
||
* @param heroHpRatio 英雄血量比例 (0-1)
|
||
*/
|
||
export function calculateBudget(timeInSeconds: number, heroHpRatio: number = 1.0): number {
|
||
const config = getRogueConfig();
|
||
|
||
// 基础预算
|
||
const Base_Budget = config.baseBudget;
|
||
|
||
// 时间因子:随时间增加难度
|
||
const timeFactor = 1 + (timeInSeconds / 60) * config.timeDifficultyFactor;
|
||
|
||
// 强度乘数 (DDA):根据英雄状态调整
|
||
let intensityMultiplier = 1.0;
|
||
if (heroHpRatio < config.survivalHpThreshold) {
|
||
// 绝地求生:血量低于阈值时预算缩减
|
||
intensityMultiplier = config.survivalBudgetMultiplier;
|
||
}
|
||
|
||
// 公式: Budget(t) = Base_Budget * (1 + t/60 * Factor) * Intensity_Multiplier
|
||
return Math.floor(Base_Budget * timeFactor * intensityMultiplier);
|
||
}
|
||
|
||
/**
|
||
* 根据预算生成怪物列表
|
||
* @param timeInSeconds 游戏时间(秒)
|
||
* @param heroHpRatio 英雄血量比例
|
||
*/
|
||
export function generateMonstersFromBudget(timeInSeconds: number, heroHpRatio: number = 1.0): IMonsConfig[] {
|
||
const config = getRogueConfig();
|
||
|
||
// 15分钟后只生成Boss
|
||
if (timeInSeconds >= 15 * 60) {
|
||
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 && monsters.length < config.maxSpawnPerLogic) {
|
||
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,
|
||
position: Math.floor(Math.random() * 5)
|
||
});
|
||
}
|
||
}
|
||
|
||
return monsters;
|
||
}
|
||
|
||
|
||
|
||
|
||
/**
|
||
* 计算波次因子
|
||
* @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 {
|
||
// 基础倍率:15分钟成长约 16 倍 (1 + 1.0 * 15)
|
||
// waveFactor 是 0-1 (基于15分钟)
|
||
const TIME_SCALING = 15;
|
||
const growthMultiplier = Math.pow(1 + waveFactor * TIME_SCALING, 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;
|
||
}
|
||
|
||
/**
|
||
* 无限等级经验配置
|
||
* @param level 当前等级
|
||
* @returns 升级所需经验值
|
||
*/
|
||
export function getLevelExp(level: number): number {
|
||
// 基础经验
|
||
const baseExp = 100;
|
||
// 增长因子 (每级增加20%)
|
||
const growthFactor = 1.2;
|
||
|
||
// 公式: Exp = Base * (Factor ^ (Level - 1))
|
||
// 1级: 100
|
||
// 2级: 120
|
||
// 3级: 144
|
||
// 10级: ~515
|
||
// 20级: ~3194
|
||
return Math.floor(baseExp * Math.pow(growthFactor, level - 1));
|
||
}
|
||
|
||
/**
|
||
* 计算怪物掉落金币
|
||
* @param uuid 怪物ID
|
||
* @param level 怪物等级
|
||
* @param type 怪物类型
|
||
*/
|
||
export function calculateMonsterGold(uuid: number, level: number, type: MonType): number {
|
||
const cost = MonsterCost[uuid] || 1;
|
||
|
||
// 危险值系数: cost越大越危险
|
||
let danger_ratio = 1 + cost * 0.1;
|
||
|
||
let type_ratio = 1;
|
||
if(type == MonType.BOSS) type_ratio = 10;
|
||
else if(type == MonType.ELITE) type_ratio = 3;
|
||
|
||
// 公式: 基础(10) * 类型 * 危险值 + 等级加成
|
||
const baseGold = 10;
|
||
let gold = Math.floor(baseGold * type_ratio * danger_ratio + level);
|
||
|
||
return gold;
|
||
}
|
||
|
||
// 怪物消耗点数配置
|
||
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 < 3) {
|
||
// 0-3min: 匀速群落 - 100% 战士
|
||
return [{ uuid: 5201, weight: 100 }];
|
||
} else if (minutes < 8) {
|
||
// 3-8min: 快速干扰 - 70% 战士, 30% 刺客
|
||
return [
|
||
{ uuid: 5201, weight: 70 },
|
||
{ uuid: 5301, weight: 30 }
|
||
];
|
||
} else if (minutes < 14) {
|
||
// 8-14min: 阵地博弈 - 40% 战士, 30% 刺客, 20% 攻城/治疗/精英
|
||
return [
|
||
{ uuid: 5201, weight: 40 },
|
||
{ uuid: 5301, weight: 30 },
|
||
{ uuid: 5401, weight: 10 },
|
||
{ uuid: 5603, weight: 10 },
|
||
{ uuid: 5701, weight: 10 }
|
||
];
|
||
} else {
|
||
// 15min: 剧情杀/决战 - 100% Boss
|
||
return [{ uuid: 5701, weight: 100 }];
|
||
}
|
||
}
|
||
|