You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/script/battle/config/MonsterHpCalculator.ts

174 lines
6.1 KiB

3 weeks ago
import { TableNames } from '../../game/ConfigTableData';
import type { UnitDefId } from 'db://assets/mx/module/battle';
import { logChapterMonsterHpCalc } from '../debug/MonsterHpDebugLog';
import type { ProjectLevelMonsterConfig } from './LevelMonsterConfig';
/**
* 最终 HP 额外倍率:max(1, 局外紫色及以上武器最高等级 / 5)
*/
export function calcSpecialReplicaFinalHpScale(): number {
const purpleMaxLv = Math.max(1, gg.data.project.getMaxLevelPurpleWeapon());
return Math.max(1, purpleMaxLv / 5);
}
/** 按关卡配表解析 hp_multiple */
export function resolveHpMultiple(chapterHpMultiple: number): number {
return chapterHpMultiple > 0 ? chapterHpMultiple : 1;
}
/** 读取 Monster1 系数:monster_group 定列,波次序号 waveIndex 定行 */
export function getMonster2Factor(monsterGroup: number, difficultyRow: number): number {
const row = gg.data.table.getTableData<ITableMonster1>(TableNames.Monster1, difficultyRow);
if (!row) {
console.log('[MonsterHpCalculator] Monster2 row missing', difficultyRow);
return 1;
}
const key = `diffcult${monsterGroup}` as keyof ITableMonster1;
const value = row[key];
return typeof value === 'number' && value > 0 ? value : 1;
}
/** 旧逻辑:按战斗时间查 Monster2 行(time <= row.id 的首行) */
export function getMonster2FactorByBattleTime(monsterGroup: number, battleTime: number): number {
const list = gg.data.table.getTableList<ITableMonster1>(TableNames.Monster1) ?? [];
for (const row of list) {
if (battleTime <= row.id) {
const key = `diffcult${monsterGroup}` as keyof ITableMonster1;
const value = row[key];
return typeof value === 'number' && value > 0 ? value : 1;
}
}
return 1;
}
/**
* 战力修正系数:
* - 推荐/玩家 >= 1:max(1, ratio ^ power_para)
* - 推荐/玩家 < 1:max(0.5, ratio ^ power_para2)
*/
export function calcPowerHpCoeff(
recommendPower: number,
userPower: number,
powerPara: number,
powerPara2: number = 0,
): number {
const user = Math.max(1, userPower);
const recommend = Math.max(0, recommendPower);
const ratio = recommend / user;
if (ratio >= 1) {
if (powerPara <= 0) return 1;
return Math.max(1, Math.pow(ratio, powerPara));
}
if (powerPara2 <= 0) return 1;
return Math.max(0.5, Math.pow(ratio, powerPara2));
}
/** 段位对应 weapon_para(Level 表暂无该字段时返回 1) */
export function getWeaponParaFromMaxChapter(rankId: number): number {
if (!(rankId > 0)) {
console.log('[MonsterHpCalculator] rankId 无效', rankId);
return 1;
}
const levelRow = gg.data.table.getTableData<ITableLevel>(TableNames.Level, rankId);
const v = (levelRow as { weapon_para?: number } | null)?.weapon_para;
return typeof v === 'number' && v > 0 ? v : 1;
}
export function calcPowerHpCoeffForLevel(
_levelId: number,
powerPara: number,
powerPara2: number = 0,
): number {
const power = gg.data.getCurPower();
return calcPowerHpCoeff(power, power, powerPara, powerPara2);
}
import { finitePositiveHp } from '../../../mx/tools/FiniteHp';
/**
* 策划 / 旧项目血量公式:
* floor(monster_hp × hp_multiple × port_hp × basehp × 战力修正 × weapon_para)
*/
export function calcChapterMonsterHp(
monsterId: number,
port: number,
difficultyRow: number,
config: ProjectLevelMonsterConfig,
): number {
const monster = gg.data.table.getTableData<ITableMonster>(TableNames.Monster, monsterId);
const monsterHp = finitePositiveHp(
(monster as ITableMonster & { HP?: number })?.HP ?? monster?.hp,
1,
);
const portHp = finitePositiveHp(config.portHpMult[port - 1], 1);
const baseHp = getMonster2Factor(config.monsterGroup, difficultyRow);
const powerCoeff = calcPowerHpCoeffForLevel(
config.levelId,
config.powerPara,
config.powerPara2,
);
const hpMultiple = finitePositiveHp(config.hpMultiple, 1);
const weaponPara = finitePositiveHp(config.weaponPara, 1);
const raw = monsterHp * hpMultiple * portHp * baseHp * powerCoeff * weaponPara;
if (!Number.isFinite(raw)) {
console.error(`[MonsterHpCalculator] invalid hp raw monsterId=${monsterId} port=${port} difficultyRow=${difficultyRow} monsterHp=${monsterHp} hpMultiple=${hpMultiple} portHp=${portHp} baseHp=${baseHp} powerCoeff=${powerCoeff} weaponPara=${weaponPara}`);
}
let finalHp = finitePositiveHp(Math.floor(raw), 1);
const scale = finitePositiveHp(config.specialReplicaHpScale, 1);
if (scale !== 1) {
finalHp = finitePositiveHp(Math.floor(finalHp * scale), finalHp);
}
logChapterMonsterHpCalc({
monsterId,
port,
difficultyRow,
config,
monsterHp,
portHp,
baseHp,
powerCoeff,
hpMultiple,
weaponPara,
raw,
finalHp,
});
return finalHp;
}
/** 旧 MonsterDataManager 兼容:返回 basehp × 战力修正(不含 monster_hp / hp_multiple / weapon_para) */
export function calcMonsterHpBaseByBattleTime(
monsterGroup: number,
battleTime: number,
recommendPower: number,
powerPara: number,
powerPara2: number = 0,
): number {
const m2 = getMonster2FactorByBattleTime(monsterGroup, battleTime);
const powerCoeff = calcPowerHpCoeff(recommendPower, gg.data.getCurPower(), powerPara, powerPara2);
return m2 * powerCoeff;
}
export function makeMonsterUnitDefId(monsterId: number, port: number, difficultyRow: number): UnitDefId {
return `monster_${monsterId}_p${port}_r${difficultyRow}`;
}
export function parseMonsterUnitDefId(unitDefId: UnitDefId): {
monsterId: number;
port: number;
difficultyRow: number;
} | null {
const m = /^monster_(\d+)_p(\d+)_r(\d+)$/.exec(unitDefId);
if (!m) return null;
return {
monsterId: parseInt(m[1], 10),
port: parseInt(m[2], 10),
difficultyRow: parseInt(m[3], 10),
};
}
export function parseLegacyMonsterId(unitDefId: UnitDefId): number {
const m = /^monster_(\d+)$/.exec(unitDefId);
return m ? parseInt(m[1], 10) : 0;
}