refactor(roguemap): 重构怪物ap计算逻辑,脱离总强度框架
1. 新增怪物ap独立计算体系,挂钩玩家平均攻击与回合递进 2. 废弃原ap_mul配置字段,保留仅用于兼容旧配置 3. 新增类型系数、成长曲线函数与英雄平均攻击统计逻辑 4. 修复原ap成长被总强度熨平的问题,提升后期战斗威胁感
This commit is contained in:
@@ -9,12 +9,16 @@
|
||||
* 4. MonSkillSet - 怪物技能池(atking / atked / dead 等全触发类型)
|
||||
* 5. RogueSpawningEngine - 生成引擎(按英雄强度反推怪物强度)
|
||||
*
|
||||
* 核心公式(最终强度 = heroPower × typeRatio × power_adjust × DDA × hp/ap_mul):
|
||||
* 核心公式:
|
||||
* heroPower = Σ calcHeroPower(HeroInfo[uuid], lv) (场上存活英雄)
|
||||
* targetPower = heroPower × 回合类型系数 × wave.power_adjust × DynamicTuner.factor
|
||||
* hpScale = targetPower × wave.hp_mul ÷ Σ 怪物基础强度
|
||||
* apScale = targetPower × wave.ap_mul ÷ Σ 怪物基础强度
|
||||
* 每只怪: hp ×= hpScale, ap ×= apScale
|
||||
* 怪物 hp = base_hp × hpScale (走总强度框架,自动匹配玩家)
|
||||
*
|
||||
* 怪物 ap 独立挂钩玩家输出(不走总强度,避免被 hp 挤占导致"涨不动"):
|
||||
* 怪物 ap = 玩家平均攻击 × getApWaveGrowth(wave) × AP_RELATIVE × 类型系数
|
||||
* 回合递进取线性 1.0 → 3.0(wave 1 → 20)
|
||||
* 说明:ap_mul 字段已废弃(保留字段仅为兼容配置表,不再参与计算)
|
||||
*
|
||||
* 回合节奏:
|
||||
* - 最大 20 回合,第 20 回合通关
|
||||
@@ -87,6 +91,19 @@ export const SPAWN_INTERVAL_BY_TYPE: Record<WaveType, number> = {
|
||||
[WaveType.Relax]: 0.12,
|
||||
};
|
||||
|
||||
/**
|
||||
* 怪物 ap 回合递进曲线(挂钩玩家输出,独立于总强度框架)。
|
||||
* 怪物 ap = 玩家平均攻击 × AP_WAVE_GROWTH(wave) × AP_RELATIVE × 怪物类型系数。
|
||||
* 线性 1.0 → 3.0(wave 1 → 20),保证后期伤害有可感知的爬升。
|
||||
*/
|
||||
export function getApWaveGrowth(wave: number): number {
|
||||
const t = Math.min(Math.max(wave, 1), MAX_WAVE);
|
||||
return 1.0 + (t - 1) / (MAX_WAVE - 1) * 2.0; // wave1=1.0 → wave20=3.0
|
||||
}
|
||||
|
||||
/** 怪物 ap 相对玩家平均攻击的基础系数(<1 保证单怪不至于一击致命,由 Boss/精英类型再上浮) */
|
||||
export const AP_RELATIVE = 0.6;
|
||||
|
||||
/** 回合战斗超时(秒):超过后强制结束回合(残留怪销毁并扣留存分,DynamicTuner 因 clearTime 过大自动放水) */
|
||||
export const WAVE_TIMEOUT = 75;
|
||||
|
||||
@@ -360,7 +377,7 @@ export interface WaveConfig {
|
||||
squad_pool: string[];
|
||||
/** HP 成长乘区(并入强度缩放分子,终值 = heroPower × 系数 × hp_mul ÷ Σ基础强度) */
|
||||
hp_mul: number;
|
||||
/** AP 成长乘区(同 hp_mul,独立控制怪物肉度与输出的成长比例) */
|
||||
/** @deprecated 已废弃:ap 改为挂钩玩家输出 × getApWaveGrowth,本字段不再参与计算(保留仅为兼容配置表结构) */
|
||||
ap_mul: number;
|
||||
/** 强度微调(放水 / 加压,默认 1.0) */
|
||||
power_adjust?: number;
|
||||
@@ -594,8 +611,10 @@ export class RogueSpawningEngine {
|
||||
monsters.push(boss);
|
||||
}
|
||||
|
||||
// 5. 按英雄强度反推缩放系数(hp_mul/ap_mul 并入目标强度乘区,而非预先乘到怪物上,
|
||||
// 保证配置表语义单一:最终强度 = heroPower × typeRatio × power_adjust × DDA × hp/ap_mul)
|
||||
// 5. 属性计算:
|
||||
// - hp 走总强度缩放框架(自动匹配玩家战力):hp = base_hp × hpScale
|
||||
// - ap 与总强度脱钩,挂钩玩家输出 × 回合递进(解决"ap 随回合涨不动":
|
||||
// 原方案 ap 被总强度封顶 + hp/ap 共用分母,增长被熨平到每回合 5~8%)
|
||||
const totalBasePower = monsters.reduce((sum, m) => {
|
||||
const info = HeroInfo[m.uuid];
|
||||
return sum + (info ? calcHeroPower(info, 1) : m.hp + m.ap);
|
||||
@@ -603,10 +622,12 @@ export class RogueSpawningEngine {
|
||||
|
||||
if (totalBasePower > 0 && targetPower > 0) {
|
||||
const hpScale = (targetPower * cfg.hp_mul) / totalBasePower;
|
||||
const apScale = (targetPower * cfg.ap_mul) / totalBasePower;
|
||||
const heroAvgAp = this.getCurrentHeroAttack();
|
||||
const apGrowth = getApWaveGrowth(wave);
|
||||
for (const m of monsters) {
|
||||
m.hp = Math.max(1, Math.round(m.hp * hpScale));
|
||||
m.ap = Math.max(1, Math.round(m.ap * apScale));
|
||||
// ap = 玩家平均攻击 × 回合递进 × 基础系数 × 类型系数(Boss/护卫/精英在上游已上浮 base_ap)
|
||||
m.ap = Math.max(1, Math.round(heroAvgAp * apGrowth * AP_RELATIVE * this.getApTypeFactor(m)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,6 +702,25 @@ export class RogueSpawningEngine {
|
||||
return Math.max(total, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算场上存活英雄的输出强度(用于怪物 ap 挂钩玩家,独立于总强度框架)。
|
||||
* 取玩家英雄的"实时 ap × 数量",代表玩家当前能打出/承受的攻击量级。
|
||||
* 怪物 ap 以此为锚,保证玩家输出越高、被反击越痛(威胁感跟随成长)。
|
||||
*/
|
||||
private getCurrentHeroAttack(): number {
|
||||
let totalAp = 0;
|
||||
let count = 0;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (!attrs || attrs.is_dead || attrs.fac !== FacSet.HERO) return;
|
||||
totalAp += Math.max(1, attrs.ap || 1);
|
||||
count++;
|
||||
});
|
||||
// 兜底:无英雄时返回基准攻击,避免除零
|
||||
if (count === 0) return 30;
|
||||
return totalAp / count; // 平均单英雄攻击
|
||||
}
|
||||
|
||||
/** 测试模式:完全绕过引擎逻辑 */
|
||||
private generateTestWave(waveNumber: number): GeneratedMonster[] {
|
||||
const growth = 1 + (waveNumber - 1) * TestModeConfig.growthRatePerWave;
|
||||
@@ -764,6 +804,21 @@ export class RogueSpawningEngine {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 怪物 ap 类型系数:Boss/护卫上浮,远程/召唤稍降(避免玻璃大炮互秒),近战/重甲基准。
|
||||
* 配合 getApWaveGrowth 使用,让"谁更疼"有明确的类型区分。
|
||||
*/
|
||||
private getApTypeFactor(m: GeneratedMonster): number {
|
||||
if (m.isBoss) return 2.2; // Boss 单发重,符合"压场"定位
|
||||
if (m.isBossGuard) return 1.2; // 护卫略高于杂鱼
|
||||
switch (m.type) {
|
||||
case MonType.Long: return 0.9;
|
||||
case MonType.Summoner: return 0.7;
|
||||
case MonType.Assassin: return 1.1;
|
||||
default: return 1.0; // Melee / Heavy
|
||||
}
|
||||
}
|
||||
|
||||
/** 生成 Boss 护卫队(复用 boss_guard 小队模板),与 Boss 同批压轴进场 */
|
||||
private makeBossGuards(wave: number): GeneratedMonster[] {
|
||||
const squad = SquadLibrary["boss_guard"];
|
||||
|
||||
Reference in New Issue
Block a user