Files
pixelheros/assets/script/game/map/RogueConfig.ts
walkpan d0f88708c6 feat(gameplay): 重新平衡游戏经济、英雄属性和怪物配置
调整游戏核心平衡参数以优化15分钟游戏体验:
1. 提升抽卡和升级金币消耗(CHOU_GOLD 5→100,LVUP_GOLD 10→50)
2. 重制英雄基础属性和成长值(战士HP 200→300,法师AP 14→40)
3. 优化怪物生成逻辑和属性曲线(BOSS HP 25000→2000)
4. 更新经济系统公式和波次权重配置
2026-01-16 23:36:43 +08:00

425 lines
13 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 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 }];
}
}