Files
pixelheros/assets/script/game/map/RogueConfig.ts
panw 26b463048f fix(rogue): 调整怪物金币和经验产出倍率以平衡游戏经济
将怪物金币和经验的基础产出倍率从5提升至8,并微调经验公式的基础系数和成长因子。
这是因为之前减少了同屏最大怪物数量,需要提高单体收益来保持总产出平衡,确保玩家升级节奏符合预期。
2026-01-30 16:57:36 +08:00

450 lines
14 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: 5, // 默认同屏5只 - 降低数量,提高单体质量
spawnLogicInterval: 1.0, // 每秒计算一次
spawnInterval: 2.0, // 队列出怪间隔
baseBudget: 1.0, // 基础预算
timeDifficultyFactor: 0.5, // 每分钟增加50%预算
survivalHpThreshold: 0.4, // 40%血量触发保护
survivalBudgetMultiplier: 0.7, // 保护时预算打7折
maxSpawnPerLogic: 2 // 单次最多生成2只
};
// 精英怪和Boss刷新时间配置 (时间单位: 秒)
export const SpecialMonsterSchedule = [
{ time: 60, uuid: 5601, type: MonType.ELITE, level: 5, desc: "1分钟: 精英自爆兵" },
{ time: 180, uuid: 5601, type: MonType.ELITE, level: 10, desc: "3分钟: 精英自爆兵" },
{ time: 300, uuid: 5701, type: MonType.BOSS, level: 15, desc: "5分钟: 兽人首领" },
{ time: 600, uuid: 5701, type: MonType.BOSS, level: 25, desc: "10分钟: 兽人首领" },
{ time: 900, uuid: 5701, type: MonType.BOSS, level: 30, desc: "15分钟: 最终Boss" }
];
// 当前配置实例
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();
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;
// 随机刷怪只生成普通怪精英和Boss由固定时间控制
let type = MonType.NORMAL;
// 即使随机到了高Cost怪在这里也只按普通怪处理或者在配置中彻底移除高Cost怪
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分钟时达到最大)
* @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);
// 质量系数数量减至10(原50的1/5)质量x5
const qualityRatio = 5.0;
// 根据怪物类型应用额外的倍率
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 * qualityRatio;
const ap = applyGrowthFormula(baseMonster.ap, waveFactor, GrowthType.LINEAR) * typeMultiplier * qualityRatio;
const speed = applyGrowthFormula(baseMonster.speed, waveFactor, GrowthType.LOGARITHMIC);
// MP和DEF使用线性成长 (应用质量系数)
const mp = applyGrowthFormula(baseMonster.mp, waveFactor, GrowthType.LINEAR) * qualityRatio;
const def = applyGrowthFormula(baseMonster.def, waveFactor, GrowthType.LINEAR) * typeMultiplier * qualityRatio;
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;
// 数量减至1/5收益倍率提升至 8 (原5) 以保持总产出平衡
let gold = Math.floor((baseGold * type_ratio * danger_ratio + level) * 8);
return gold;
}
/**
* 计算怪物经验值
* 目标让玩家在13分钟左右升到20级
* @param uuid 怪物ID
* @param level 怪物等级
*/
export function calculateMonsterExp(uuid: number, level: number): number {
const cost = MonsterCost[uuid] || 1;
// 基础系数 1.0 (原0.8),成长因子 1.15 (原1.1)
// 这样设计是为了对抗升级所需经验的指数增长 (1.2^L)
// 同时也补偿了因为最大同屏数量减少(10->5)导致的怪物总量减少
// 新公式下13分钟大约能产出 19000 经验,满足升到 20 级所需的 15464 经验
// 数量大幅减少,单体收益倍率提升至 8 (原5)
return Math.max(1, Math.floor(cost * 1.0 * Math.pow(1.15, level - 1) * 8));
}
// 怪物消耗点数配置
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: 阵地博弈 - 移除精英怪,只保留普通怪
return [
{ uuid: 5201, weight: 40 },
{ uuid: 5301, weight: 30 },
{ uuid: 5401, weight: 15 },
{ uuid: 5603, weight: 15 }
];
} else {
// 15min+: 混合兵种Boss由固定时间控制
return [
{ uuid: 5201, weight: 30 },
{ uuid: 5301, weight: 30 },
{ uuid: 5401, weight: 20 },
{ uuid: 5603, weight: 20 }
];
}
}